{
Microchip LAN78xx USB Ethernet Driver.

Copyright (C) 2018 - SoftOz Pty Ltd.

Arch
====

 <All>

Boards
======

 Raspberry Pi 3 - Model B+
 
Licence
=======

 LGPLv2.1 with static linking exception (See COPYING.modifiedLGPL.txt)
 
Credits
=======

 Information for this unit was obtained from:

  Linux - \drivers\net\usb\lan78xx.c - Copyright (C) 2015 Microchip Technology

References
==========

 Product Information
 
  https://www.microchip.com/wwwproducts/en/LAN7800
 
 Datasheet
 
  http://ww1.microchip.com/downloads/en/DeviceDoc/00001992F.pdf
 
Microchip LAN78xx
=================

The Microchip LAN78xx is a USB 3.1 to 10/100/1000 Gigabit Ethernet bridge device which supports
10BASE-T, 100BASE-TX and 1000BASE-T. Both USB 2.0 High Speed and USB 3.1 Super Speed interfaces
are supported using a pair of Bulk Endpoints and an Interrupt Endpoint for signalling status
changes and other events.

The Raspberry Pi 3B+ contains a variant of this device called a LAN7515 which is a combination
device containing a LAN78xx Gigabit Ethernet controller and two 4 port USB 2.0 compatible hubs.

The hubs will be detected and bound by the default USB Hub driver when they are enumerated by the
USB core.

}

{$mode delphi} {Default to Delphi compatible syntax}
{$H+}          {Default to AnsiString}
{$inline on}   {Allow use of Inline procedures}

unit LAN78XX; 
  
interface

uses GlobalConfig,GlobalConst,GlobalTypes,Platform,Threads,Devices,USB,Network,SysUtils;

{==============================================================================}
{Global definitions}
{$INCLUDE ..\core\GlobalDefines.inc}

{==============================================================================}
const
 {LAN78XX specific constants}
 LAN78XX_NETWORK_DESCRIPTION = 'Microchip LAN78XX USB Ethernet Adapter';  {Description of LAN78XX device}
 
 LAN78XX_DRIVER_NAME = 'Microchip LAN78XX USB Ethernet Adapter Driver'; {Name of LAN78XX driver}
 
 LAN78XX_USB_VENDOR_ID  = $0424;
 LAN7800_USB_PRODUCT_ID	= $7800;
 LAN7850_USB_PRODUCT_ID = $7850;
  
 LAN78XX_DEVICE_ID_COUNT = 2; {Number of supported Device IDs}
 
 LAN78XX_DEVICE_ID:array[0..LAN78XX_DEVICE_ID_COUNT - 1] of TUSBDeviceId = (
  (idVendor:LAN78XX_USB_VENDOR_ID;idProduct:LAN7800_USB_PRODUCT_ID),  {LAN7800/7801}
  (idVendor:LAN78XX_USB_VENDOR_ID;idProduct:LAN7850_USB_PRODUCT_ID)); {LAN7850}

 LAN78XX_SS_USB_PKT_SIZE = 1024;
 LAN78XX_HS_USB_PKT_SIZE = 512;
 LAN78XX_FS_USB_PKT_SIZE = 64;

 LAN78XX_MAX_RX_FIFO_SIZE = 12 * 1024;       {Maximum size 12KB}
 LAN78XX_MAX_TX_FIFO_SIZE = 12 * 1024;       {Maximum size 12KB}
 LAN78XX_DEFAULT_BURST_CAP_SIZE = 32 * 1024; {Originally LAN78XX_MAX_RX_FIFO_SIZE (12 * 1024)}
 LAN78XX_DEFAULT_BULK_IN_DELAY = $0800;      {2048 x 16.667 ns = 34.133 us}
 LAN78XX_MAX_SINGLE_PACKET_SIZE = 9000;
 
 LAN78XX_TX_OVERHEAD = 8;  {TX Command A + TX Command B}
 LAN78XX_RX_OVERHEAD = 10; {RX Command A + RX Command B + RX Command C}
 LAN78XX_RXW_PADDING = 2;

 LAN78XX_RX_MAX_QUEUE_MEMORY = 512 * (ETHERNET_MAX_PACKET_SIZE + LAN78XX_RX_OVERHEAD); {Originally 60 * 1518}
 LAN78XX_TX_MAX_QUEUE_MEMORY = 64 * (ETHERNET_MAX_PACKET_SIZE + LAN78XX_TX_OVERHEAD); {Originally 60 * 1518}
 
 LAN78XX_EEPROM_MAGIC = $78A5;
 LAN78XX_OTP_MAGIC = $78F3;

 LAN78XX_PHY_ADDRESS = 1;
 
 LAN78XX_EEPROM_INDICATOR = $A5;
 LAN78XX_EEPROM_MAC_OFFSET = $01;
 LAN78XX_MAX_EEPROM_SIZE = 512;
 LAN78XX_OTP_INDICATOR_1 = $F3;
 LAN78XX_OTP_INDICATOR_2 = $F7;
 
 {LAN78XX USB Vendor Requests}
 LAN78XX_USB_VENDOR_REQUEST_WRITE_REGISTER = $A0;
 LAN78XX_USB_VENDOR_REQUEST_READ_REGISTER  = $A1;
 LAN78XX_USB_VENDOR_REQUEST_GET_STATS      = $A2;
 
 {Interrupt Endpoint status word bitfields}
 LAN78XX_INT_ENP_EEE_START_TX_LPI_INT = (1 shl 26);
 LAN78XX_INT_ENP_EEE_STOP_TX_LPI_INT  = (1 shl 25);
 LAN78XX_INT_ENP_EEE_RX_LPI_INT       = (1 shl 24);
 LAN78XX_INT_ENP_RDFO_INT             = (1 shl 22);
 LAN78XX_INT_ENP_TXE_INT              = (1 shl 21);
 LAN78XX_INT_ENP_TX_DIS_INT           = (1 shl 19);
 LAN78XX_INT_ENP_RX_DIS_INT           = (1 shl 18);
 LAN78XX_INT_ENP_PHY_INT              = (1 shl 17);
 LAN78XX_INT_ENP_DP_INT               = (1 shl 16);
 LAN78XX_INT_ENP_MAC_ERR_INT          = (1 shl 15);
 LAN78XX_INT_ENP_TDFU_INT             = (1 shl 14);
 LAN78XX_INT_ENP_TDFO_INT             = (1 shl 13);
 LAN78XX_INT_ENP_UTX_FP_INT           = (1 shl 12);

 LAN78XX_TX_PKT_ALIGNMENT  = 4;
 LAN78XX_RX_PKT_ALIGNMENT  = 4;
 
 {Tx Command A}
 LAN78XX_TX_CMD_A_IGE       = $20000000;
 LAN78XX_TX_CMD_A_ICE       = $10000000;
 LAN78XX_TX_CMD_A_LSO       = $08000000;
 LAN78XX_TX_CMD_A_IPE       = $04000000;
 LAN78XX_TX_CMD_A_TPE       = $02000000;
 LAN78XX_TX_CMD_A_IVTG      = $01000000;
 LAN78XX_TX_CMD_A_RVTG      = $00800000;
 LAN78XX_TX_CMD_A_FCS       = $00400000;
 LAN78XX_TX_CMD_A_LEN_MASK  = $000FFFFF;

 {Tx Command B}
 LAN78XX_TX_CMD_B_MSS_SHIFT      = 16;
 LAN78XX_TX_CMD_B_MSS_MASK       = $3FFF0000;
 LAN78XX_TX_CMD_B_MSS_MIN        = 8;
 LAN78XX_TX_CMD_B_VTAG_MASK      = $0000FFFF;
 LAN78XX_TX_CMD_B_VTAG_PRI_MASK  = $0000E000;
 LAN78XX_TX_CMD_B_VTAG_CFI_MASK  = $00001000;
 LAN78XX_TX_CMD_B_VTAG_VID_MASK  = $00000FFF;

 {Rx Command A}
 LAN78XX_RX_CMD_A_ICE          = $80000000;
 LAN78XX_RX_CMD_A_TCE          = $40000000;
 LAN78XX_RX_CMD_A_CSE_MASK     = $C0000000;
 LAN78XX_RX_CMD_A_IPV          = $20000000;
 LAN78XX_RX_CMD_A_PID_MASK     = $18000000;
 LAN78XX_RX_CMD_A_PID_NONE_IP  = $00000000;
 LAN78XX_RX_CMD_A_PID_TCP_IP   = $08000000;
 LAN78XX_RX_CMD_A_PID_UDP_IP   = $10000000;
 LAN78XX_RX_CMD_A_PID_IP       = $18000000;
 LAN78XX_RX_CMD_A_PFF          = $04000000;
 LAN78XX_RX_CMD_A_BAM          = $02000000;
 LAN78XX_RX_CMD_A_MAM          = $01000000;
 LAN78XX_RX_CMD_A_FVTG         = $00800000;
 LAN78XX_RX_CMD_A_RED          = $00400000;
 LAN78XX_RX_CMD_A_RX_ERRS_MASK = $C03F0000;
 LAN78XX_RX_CMD_A_RWT          = $00200000;
 LAN78XX_RX_CMD_A_RUNT         = $00100000;
 LAN78XX_RX_CMD_A_LONG         = $00080000;
 LAN78XX_RX_CMD_A_RXE          = $00040000;
 LAN78XX_RX_CMD_A_DRB          = $00020000;
 LAN78XX_RX_CMD_A_FCS          = $00010000;
 LAN78XX_RX_CMD_A_UAM          = $00008000;
 LAN78XX_RX_CMD_A_ICSM         = $00004000;
 LAN78XX_RX_CMD_A_LEN_MASK     = $00003FFF;

 {Rx Command B}
 LAN78XX_RX_CMD_B_CSUM_SHIFT     = 16;
 LAN78XX_RX_CMD_B_CSUM_MASK      = $FFFF0000;
 LAN78XX_RX_CMD_B_VTAG_MASK      = $0000FFFF;
 LAN78XX_RX_CMD_B_VTAG_PRI_MASK  = $0000E000;
 LAN78XX_RX_CMD_B_VTAG_CFI_MASK  = $00001000;
 LAN78XX_RX_CMD_B_VTAG_VID_MASK  = $00000FFF;

 {Rx Command C}
 LAN78XX_RX_CMD_C_WAKE_SHIFT     = 15;
 LAN78XX_RX_CMD_C_WAKE           = $8000;
 LAN78XX_RX_CMD_C_REF_FAIL_SHIFT = 14;
 LAN78XX_RX_CMD_C_REF_FAIL       = $4000;

 {System Control and Status Registers}
 LAN78XX_NUMBER_OF_REGS  = 193;

 {DEVICE ID AND REVISION REGISTER (ID_REV)}
 LAN78XX_ID_REV    = $00;
 LAN78XX_ID_REV_CHIP_ID_MASK  = $FFFF0000;
 LAN78XX_ID_REV_CHIP_REV_MASK = $0000FFFF;
 LAN78XX_ID_REV_CHIP_ID_7800  = $7800;
 LAN78XX_ID_REV_CHIP_ID_7850  = $7850;

 {FGPA REVISION REGISTER (FPGA_REV)}
 LAN78XX_FPGA_REV   = $04;
 LAN78XX_FPGA_REV_MINOR_MASK  = $0000FF00;
 LAN78XX_FPGA_REV_MAJOR_MASK  = $000000FF;

 {INTERRUPT STATUS REGISTER (INT_STS)}
 LAN78XX_INT_STS    = $0C;
 LAN78XX_INT_STS_CLEAR_ALL       = $FFFFFFFF;
 LAN78XX_INT_STS_EEE_TX_LPI_STRT = $04000000;
 LAN78XX_INT_STS_EEE_TX_LPI_STOP = $02000000;
 LAN78XX_INT_STS_EEE_RX_LPI      = $01000000;
 LAN78XX_INT_STS_RDFO            = $00400000;
 LAN78XX_INT_STS_TXE             = $00200000;
 LAN78XX_INT_STS_TX_DIS          = $00080000;
 LAN78XX_INT_STS_RX_DIS          = $00040000;
 LAN78XX_INT_STS_PHY_INT         = $00020000;
 LAN78XX_INT_STS_DP_INT          = $00010000;
 LAN78XX_INT_STS_MAC_ERR         = $00008000;
 LAN78XX_INT_STS_TDFU            = $00004000;
 LAN78XX_INT_STS_TDFO            = $00002000;
 LAN78XX_INT_STS_UFX_FP          = $00001000;
 LAN78XX_INT_STS_GPIO_MASK       = $00000FFF;
 LAN78XX_INT_STS_GPIO11          = $00000800;
 LAN78XX_INT_STS_GPIO10          = $00000400;
 LAN78XX_INT_STS_GPIO9           = $00000200;
 LAN78XX_INT_STS_GPIO8           = $00000100;
 LAN78XX_INT_STS_GPIO7           = $00000080;
 LAN78XX_INT_STS_GPIO6           = $00000040;
 LAN78XX_INT_STS_GPIO5           = $00000020;
 LAN78XX_INT_STS_GPIO4           = $00000010;
 LAN78XX_INT_STS_GPIO3           = $00000008;
 LAN78XX_INT_STS_GPIO2           = $00000004;
 LAN78XX_INT_STS_GPIO1           = $00000002;
 LAN78XX_INT_STS_GPIO0           = $00000001;

 {HARDWARE CONFIGURATION REGISTER (HW_CFG)}
 LAN78XX_HW_CFG     = $010;
 LAN78XX_HW_CFG_CLK125_EN           = $02000000;
 LAN78XX_HW_CFG_REFCLK25_EN         = $01000000;
 LAN78XX_HW_CFG_LED3_EN             = $00800000;
 LAN78XX_HW_CFG_LED2_EN             = $00400000;
 LAN78XX_HW_CFG_LED1_EN             = $00200000;
 LAN78XX_HW_CFG_LED0_EN             = $00100000;
 LAN78XX_HW_CFG_EEE_PHY_LUSU        = $00020000;
 LAN78XX_HW_CFG_EEE_TSU             = $00010000;
 LAN78XX_HW_CFG_NETDET_STS          = $00008000;
 LAN78XX_HW_CFG_NETDET_EN           = $00004000;
 LAN78XX_HW_CFG_EEM                 = $00002000;
 LAN78XX_HW_CFG_RST_PROTECT         = $00001000;
 LAN78XX_HW_CFG_CONNECT_BUF         = $00000400;
 LAN78XX_HW_CFG_CONNECT_EN          = $00000200;
 LAN78XX_HW_CFG_CONNECT_POL         = $00000100;
 LAN78XX_HW_CFG_SUSPEND_N_SEL_MASK  = $000000C0;
 LAN78XX_HW_CFG_SUSPEND_N_SEL_2     = $00000000;
 LAN78XX_HW_CFG_SUSPEND_N_SEL_12N   = $00000040;
 LAN78XX_HW_CFG_SUSPEND_N_SEL_012N  = $00000080;
 LAN78XX_HW_CFG_SUSPEND_N_SEL_0123N = $000000C0;
 LAN78XX_HW_CFG_SUSPEND_N_POL       = $00000020;
 LAN78XX_HW_CFG_MEF                 = $00000010;
 LAN78XX_HW_CFG_ETC                 = $00000008;
 LAN78XX_HW_CFG_LRST                = $00000002;
 LAN78XX_HW_CFG_SRST                = $00000001;

 {POWER MANAGEMENT CONTROL REGISTER (PMT_CTL)}
 LAN78XX_PMT_CTL    = $014;
 LAN78XX_PMT_CTL_EEE_WAKEUP_EN    = $00002000;
 LAN78XX_PMT_CTL_EEE_WUPS         = $00001000;
 LAN78XX_PMT_CTL_MAC_SRST         = $00000800;
 LAN78XX_PMT_CTL_PHY_PWRUP        = $00000400;
 LAN78XX_PMT_CTL_RES_CLR_WKP_MASK = $00000300;
 LAN78XX_PMT_CTL_RES_CLR_WKP_STS  = $00000200;
 LAN78XX_PMT_CTL_RES_CLR_WKP_EN   = $00000100;
 LAN78XX_PMT_CTL_READY            = $00000080;
 LAN78XX_PMT_CTL_SUS_MODE_MASK    = $00000060;
 LAN78XX_PMT_CTL_SUS_MODE_0       = $00000000;
 LAN78XX_PMT_CTL_SUS_MODE_1       = $00000020;
 LAN78XX_PMT_CTL_SUS_MODE_2       = $00000040;
 LAN78XX_PMT_CTL_SUS_MODE_3       = $00000060;
 LAN78XX_PMT_CTL_PHY_RST          = $00000010;
 LAN78XX_PMT_CTL_WOL_EN           = $00000008;
 LAN78XX_PMT_CTL_PHY_WAKE_EN      = $00000004;
 LAN78XX_PMT_CTL_WUPS_MASK        = $00000003;
 LAN78XX_PMT_CTL_WUPS_MLT         = $00000003;
 LAN78XX_PMT_CTL_WUPS_MAC         = $00000002;
 LAN78XX_PMT_CTL_WUPS_PHY         = $00000001;

 {GENERAL PURPOSE IO CONFIGURATION 0 REGISTER (GPIO_CFG0)}
 LAN78XX_GPIO_CFG0   = $018;
 LAN78XX_GPIO_CFG0_GPIOEN_MASK  = $0000F000;
 LAN78XX_GPIO_CFG0_GPIOEN3  = $00008000;
 LAN78XX_GPIO_CFG0_GPIOEN2  = $00004000;
 LAN78XX_GPIO_CFG0_GPIOEN1  = $00002000;
 LAN78XX_GPIO_CFG0_GPIOEN0  = $00001000;
 LAN78XX_GPIO_CFG0_GPIOBUF_MASK  = $00000F00;
 LAN78XX_GPIO_CFG0_GPIOBUF3  = $00000800;
 LAN78XX_GPIO_CFG0_GPIOBUF2  = $00000400;
 LAN78XX_GPIO_CFG0_GPIOBUF1  = $00000200;
 LAN78XX_GPIO_CFG0_GPIOBUF0  = $00000100;
 LAN78XX_GPIO_CFG0_GPIODIR_MASK  = $000000F0;
 LAN78XX_GPIO_CFG0_GPIODIR3  = $00000080;
 LAN78XX_GPIO_CFG0_GPIODIR2  = $00000040;
 LAN78XX_GPIO_CFG0_GPIODIR1  = $00000020;
 LAN78XX_GPIO_CFG0_GPIODIR0  = $00000010;
 LAN78XX_GPIO_CFG0_GPIOD_MASK  = $0000000F;
 LAN78XX_GPIO_CFG0_GPIOD3  = $00000008;
 LAN78XX_GPIO_CFG0_GPIOD2  = $00000004;
 LAN78XX_GPIO_CFG0_GPIOD1  = $00000002;
 LAN78XX_GPIO_CFG0_GPIOD0  = $00000001;

 {GENERAL PURPOSE IO CONFIGURATION 1 REGISTER (GPIO_CFG1)}
 LAN78XX_GPIO_CFG1   = $01C;
 LAN78XX_GPIO_CFG1_GPIOEN_MASK  = $FF000000;
 LAN78XX_GPIO_CFG1_GPIOEN11  = $80000000;
 LAN78XX_GPIO_CFG1_GPIOEN10  = $40000000;
 LAN78XX_GPIO_CFG1_GPIOEN9  = $20000000;
 LAN78XX_GPIO_CFG1_GPIOEN8  = $10000000;
 LAN78XX_GPIO_CFG1_GPIOEN7  = $08000000;
 LAN78XX_GPIO_CFG1_GPIOEN6  = $04000000;
 LAN78XX_GPIO_CFG1_GPIOEN5  = $02000000;
 LAN78XX_GPIO_CFG1_GPIOEN4  = $01000000;
 LAN78XX_GPIO_CFG1_GPIOBUF_MASK  = $00FF0000;
 LAN78XX_GPIO_CFG1_GPIOBUF11  = $00800000;
 LAN78XX_GPIO_CFG1_GPIOBUF10  = $00400000;
 LAN78XX_GPIO_CFG1_GPIOBUF9  = $00200000;
 LAN78XX_GPIO_CFG1_GPIOBUF8  = $00100000;
 LAN78XX_GPIO_CFG1_GPIOBUF7  = $00080000;
 LAN78XX_GPIO_CFG1_GPIOBUF6  = $00040000;
 LAN78XX_GPIO_CFG1_GPIOBUF5  = $00020000;
 LAN78XX_GPIO_CFG1_GPIOBUF4  = $00010000;
 LAN78XX_GPIO_CFG1_GPIODIR_MASK  = $0000FF00;
 LAN78XX_GPIO_CFG1_GPIODIR11  = $00008000;
 LAN78XX_GPIO_CFG1_GPIODIR10  = $00004000;
 LAN78XX_GPIO_CFG1_GPIODIR9  = $00002000;
 LAN78XX_GPIO_CFG1_GPIODIR8  = $00001000;
 LAN78XX_GPIO_CFG1_GPIODIR7  = $00000800;
 LAN78XX_GPIO_CFG1_GPIODIR6  = $00000400;
 LAN78XX_GPIO_CFG1_GPIODIR5  = $00000200;
 LAN78XX_GPIO_CFG1_GPIODIR4  = $00000100;
 LAN78XX_GPIO_CFG1_GPIOD_MASK  = $000000FF;
 LAN78XX_GPIO_CFG1_GPIOD11  = $00000080;
 LAN78XX_GPIO_CFG1_GPIOD10  = $00000040;
 LAN78XX_GPIO_CFG1_GPIOD9  = $00000020;
 LAN78XX_GPIO_CFG1_GPIOD8  = $00000010;
 LAN78XX_GPIO_CFG1_GPIOD7  = $00000008;
 LAN78XX_GPIO_CFG1_GPIOD6  = $00000004;
 LAN78XX_GPIO_CFG1_GPIOD5  = $00000002;
 LAN78XX_GPIO_CFG1_GPIOD4  = $00000001;

 {GENERAL PURPOSE IO WAKE ENABLE AND POLARITY REGISTER (GPIO_WAKE)}
 LAN78XX_GPIO_WAKE   = $020;
 LAN78XX_GPIO_WAKE_GPIOPOL_MASK  = $0FFF0000;
 LAN78XX_GPIO_WAKE_GPIOPOL11  = $08000000;
 LAN78XX_GPIO_WAKE_GPIOPOL10  = $04000000;
 LAN78XX_GPIO_WAKE_GPIOPOL9  = $02000000;
 LAN78XX_GPIO_WAKE_GPIOPOL8  = $01000000;
 LAN78XX_GPIO_WAKE_GPIOPOL7  = $00800000;
 LAN78XX_GPIO_WAKE_GPIOPOL6  = $00400000;
 LAN78XX_GPIO_WAKE_GPIOPOL5  = $00200000;
 LAN78XX_GPIO_WAKE_GPIOPOL4  = $00100000;
 LAN78XX_GPIO_WAKE_GPIOPOL3  = $00080000;
 LAN78XX_GPIO_WAKE_GPIOPOL2  = $00040000;
 LAN78XX_GPIO_WAKE_GPIOPOL1  = $00020000;
 LAN78XX_GPIO_WAKE_GPIOPOL0  = $00010000;
 LAN78XX_GPIO_WAKE_GPIOWK_MASK  = $00000FFF;
 LAN78XX_GPIO_WAKE_GPIOWK11  = $00000800;
 LAN78XX_GPIO_WAKE_GPIOWK10  = $00000400;
 LAN78XX_GPIO_WAKE_GPIOWK9  = $00000200;
 LAN78XX_GPIO_WAKE_GPIOWK8  = $00000100;
 LAN78XX_GPIO_WAKE_GPIOWK7  = $00000080;
 LAN78XX_GPIO_WAKE_GPIOWK6  = $00000040;
 LAN78XX_GPIO_WAKE_GPIOWK5  = $00000020;
 LAN78XX_GPIO_WAKE_GPIOWK4  = $00000010;
 LAN78XX_GPIO_WAKE_GPIOWK3  = $00000008;
 LAN78XX_GPIO_WAKE_GPIOWK2  = $00000004;
 LAN78XX_GPIO_WAKE_GPIOWK1  = $00000002;
 LAN78XX_GPIO_WAKE_GPIOWK0  = $00000001;

 {DATA PORT SELECT REGISTER (DP_SEL)}
 LAN78XX_DP_SEL    = $024;
 LAN78XX_DP_SEL_DPRDY   = $80000000;
 LAN78XX_DP_SEL_RSEL_MASK  = $0000000F;
 LAN78XX_DP_SEL_RSEL_USB_PHY_CSRS  = $0000000F;
 LAN78XX_DP_SEL_RSEL_OTP_64BIT  = $00000009;
 LAN78XX_DP_SEL_RSEL_OTP_8BIT  = $00000008;
 LAN78XX_DP_SEL_RSEL_UTX_BUF_RAM  = $00000007;
 LAN78XX_DP_SEL_RSEL_DESC_RAM  = $00000005;
 LAN78XX_DP_SEL_RSEL_TXFIFO  = $00000004;
 LAN78XX_DP_SEL_RSEL_RXFIFO  = $00000003;
 LAN78XX_DP_SEL_RSEL_LSO  = $00000002;
 LAN78XX_DP_SEL_RSEL_VLAN_DA  = $00000001;
 LAN78XX_DP_SEL_RSEL_URXBUF  = $00000000;
 LAN78XX_DP_SEL_VHF_HASH_LEN = 16;
 LAN78XX_DP_SEL_VHF_VLAN_LEN = 128;

 {DATA PORT COMMAND REGISTER (DP_CMD)}
 LAN78XX_DP_CMD    = $028;
 LAN78XX_DP_CMD_WRITE   = $00000001;
 LAN78XX_DP_CMD_READ   = $00000000;

 {DATA PORT ADDRESS REGISTER (DP_ADDR)}
 LAN78XX_DP_ADDR    = $02C;
 LAN78XX_DP_ADDR_MASK   = $00003FFF;

 {DATA PORT DATA REGISTER (DP_DATA)}
 LAN78XX_DP_DATA    = $030;

 {EEPROM COMMAND REGISTER (E2P_CMD)}
 LAN78XX_E2P_CMD    = $040;
 LAN78XX_E2P_CMD_EPC_BUSY  = $80000000;
 LAN78XX_E2P_CMD_EPC_CMD_MASK  = $70000000;
 LAN78XX_E2P_CMD_EPC_CMD_RELOAD  = $70000000;
 LAN78XX_E2P_CMD_EPC_CMD_ERAL  = $60000000;
 LAN78XX_E2P_CMD_EPC_CMD_ERASE  = $50000000;
 LAN78XX_E2P_CMD_EPC_CMD_WRAL  = $40000000;
 LAN78XX_E2P_CMD_EPC_CMD_WRITE  = $30000000;
 LAN78XX_E2P_CMD_EPC_CMD_EWEN  = $20000000;
 LAN78XX_E2P_CMD_EPC_CMD_EWDS  = $10000000;
 LAN78XX_E2P_CMD_EPC_CMD_READ  = $00000000;
 LAN78XX_E2P_CMD_EPC_TIMEOUT  = $00000400;
 LAN78XX_E2P_CMD_EPC_DL   = $00000200;
 LAN78XX_E2P_CMD_EPC_ADDR_MASK  = $000001FF;

 {EEPROM DATA REGISTER (E2P_DATA)}
 LAN78XX_E2P_DATA   = $044;
 LAN78XX_E2P_DATA_EEPROM_DATA_MASK  = $000000FF;

 {BOS DESCRIPTOR ATTRIBUTES REGISTER (BOS_ATTR)}
 LAN78XX_BOS_ATTR   = $050;
 LAN78XX_BOS_ATTR_BLOCK_SIZE_MASK  = $000000FF;

 {SS DESCRIPTOR ATTRIBUTES REGISTER (SS_ATTR)}
 LAN78XX_SS_ATTR    = $054;
 LAN78XX_SS_ATTR_POLL_INT_MASK  = $00FF0000;
 LAN78XX_SS_ATTR_DEV_DESC_SIZE_MASK  = $0000FF00;
 LAN78XX_SS_ATTR_CFG_BLK_SIZE_MASK  = $000000FF;

 {HS DESCRIPTOR ATTRIBUTES REGISTER (HS_ATTR)}
 LAN78XX_HS_ATTR    = $058;
 LAN78XX_HS_ATTR_POLL_INT_MASK  = $00FF0000;
 LAN78XX_HS_ATTR_DEV_DESC_SIZE_MASK  = $0000FF00;
 LAN78XX_HS_ATTR_CFG_BLK_SIZE_MASK  = $000000FF;

 {FS DESCRIPTOR ATTRIBUTES REGISTER (FS_ATTR)} 
 LAN78XX_FS_ATTR    = $05C;
 LAN78XX_FS_ATTR_POLL_INT_MASK  = $00FF0000;
 LAN78XX_FS_ATTR_DEV_DESC_SIZE_MASK  = $0000FF00;
 LAN78XX_FS_ATTR_CFG_BLK_SIZE_MASK  = $000000FF;

 {STRING ATTRIBUTES REGISTER 0 (STRNG_ATTR0)}
 LAN78XX_STR_ATTR0       = $060;
 LAN78XX_STR_ATTR0_CFGSTR_DESC_SIZE_MASK    = $FF000000;
 LAN78XX_STR_ATTR0_SERSTR_DESC_SIZE_MASK    = $00FF0000;
 LAN78XX_STR_ATTR0_PRODSTR_DESC_SIZE_MASK   = $0000FF00;
 LAN78XX_STR_ATTR0_MANUF_DESC_SIZE_MASK     = $000000FF;

 {STRING ATTRIBUTES REGISTER 1 (STRNG_ATTR1)}
 LAN78XX_STR_ATTR1       = $064;
 LAN78XX_STR_ATTR1_INTSTR_DESC_SIZE_MASK    = $000000FF;

 {FLAG ATTRIBUTES REGISTER (FLAG_ATTR}
 LAN78XX_STR_FLAG_ATTR       = $068;
 LAN78XX_STR_FLAG_ATTR_PME_FLAGS_MASK     = $000000FF;

 {SOFTWARE GENERAL PURPOSE REGISTER X (SW_GPX)}
 {0x6C - 0x77}
 
 {USB CONFIGURATION REGISTER 0 (USB_CFG0)}
 LAN78XX_USB_CFG0   = $080;
 LAN78XX_USB_CFG_LPM_RESPONSE  = $80000000;
 LAN78XX_USB_CFG_LPM_CAPABILITY  = $40000000;
 LAN78XX_USB_CFG_LPM_ENBL_SLPM  = $20000000;
 LAN78XX_USB_CFG_HIRD_THR_MASK  = $1F000000;
 LAN78XX_USB_CFG_HIRD_THR_960  = $1C000000;
 LAN78XX_USB_CFG_HIRD_THR_885  = $1B000000;
 LAN78XX_USB_CFG_HIRD_THR_810  = $1A000000;
 LAN78XX_USB_CFG_HIRD_THR_735  = $19000000;
 LAN78XX_USB_CFG_HIRD_THR_660  = $18000000;
 LAN78XX_USB_CFG_HIRD_THR_585  = $17000000;
 LAN78XX_USB_CFG_HIRD_THR_510  = $16000000;
 LAN78XX_USB_CFG_HIRD_THR_435  = $15000000;
 LAN78XX_USB_CFG_HIRD_THR_360  = $14000000;
 LAN78XX_USB_CFG_HIRD_THR_285  = $13000000;
 LAN78XX_USB_CFG_HIRD_THR_210  = $12000000;
 LAN78XX_USB_CFG_HIRD_THR_135  = $11000000;
 LAN78XX_USB_CFG_HIRD_THR_60  = $10000000;
 LAN78XX_USB_CFG_MAX_BURST_BI_MASK  = $00F00000;
 LAN78XX_USB_CFG_MAX_BURST_BO_MASK  = $000F0000;
 LAN78XX_USB_CFG_MAX_DEV_SPEED_MASK = $0000E000;
 LAN78XX_USB_CFG_MAX_DEV_SPEED_SS  = $00008000;
 LAN78XX_USB_CFG_MAX_DEV_SPEED_HS  = $00000000;
 LAN78XX_USB_CFG_MAX_DEV_SPEED_FS  = $00002000;
 LAN78XX_USB_CFG_PHY_BOOST_MASK  = $00000180;
 LAN78XX_USB_CFG_PHY_BOOST_PLUS_12  = $00000180;
 LAN78XX_USB_CFG_PHY_BOOST_PLUS_8  = $00000100;
 LAN78XX_USB_CFG_PHY_BOOST_PLUS_4  = $00000080;
 LAN78XX_USB_CFG_PHY_BOOST_NORMAL  = $00000000;
 LAN78XX_USB_CFG_BIR   = $00000040;
 LAN78XX_USB_CFG_BCE   = $00000020;
 LAN78XX_USB_CFG_PORT_SWAP  = $00000010;
 LAN78XX_USB_CFG_LPM_EN   = $00000008;
 LAN78XX_USB_CFG_RMT_WKP  = $00000004;
 LAN78XX_USB_CFG_PWR_SEL  = $00000002;
 LAN78XX_USB_CFG_STALL_BO_DIS  = $00000001;

 {USB CONFIGURATION REGISTER 1 (USB_CFG1)}
 LAN78XX_USB_CFG1   = $084;
 LAN78XX_USB_CFG1_U1_TIMEOUT_MASK  = $FF000000;
 LAN78XX_USB_CFG1_U2_TIMEOUT_MASK  = $00FF0000;
 LAN78XX_USB_CFG1_HS_TOUT_CAL_MASK  = $0000E000;
 LAN78XX_USB_CFG1_DEV_U2_INIT_EN  = $00001000;
 LAN78XX_USB_CFG1_DEV_U2_EN  = $00000800;
 LAN78XX_USB_CFG1_DEV_U1_INIT_EN  = $00000400;
 LAN78XX_USB_CFG1_DEV_U1_EN  = $00000200;
 LAN78XX_USB_CFG1_LTM_ENABLE  = $00000100;
 LAN78XX_USB_CFG1_FS_TOUT_CAL_MASK  = $00000070;
 LAN78XX_USB_CFG1_SCALE_DOWN_MASK  = $00000003;
 LAN78XX_USB_CFG1_SCALE_DOWN_MODE3  = $00000003;
 LAN78XX_USB_CFG1_SCALE_DOWN_MODE2  = $00000002;
 LAN78XX_USB_CFG1_SCALE_DOWN_MODE1  = $00000001;
 LAN78XX_USB_CFG1_SCALE_DOWN_MODE0  = $00000000;

 {USB CONFIGURATION REGISTER 2 (USB_CFG2)}
 LAN78XX_USB_CFG2       = $088;
 LAN78XX_USB_CFG2_SS_DETACH_TIME_MASK     = $FFFF0000;
 LAN78XX_USB_CFG2_HS_DETACH_TIME_MASK     = $0000FFFF;

 {BURST CAP REGISTER (BURST_CAP)}
 LAN78XX_BURST_CAP   = $090;
 LAN78XX_BURST_CAP_SIZE_MASK  = $000000FF;

 {BULK-IN DELAY REGISTER (BULK_IN_DLY)}
 LAN78XX_BULK_IN_DLY   = $094;
 LAN78XX_BULK_IN_DLY_MASK  = $0000FFFF;

 {INTERRUPT ENDPOINT CONTROL REGISTER (INT_EP_CTL)}
 LAN78XX_INT_EP_CTL   = $098;
 LAN78XX_INT_EP_INTEP_ON  = $80000000;
 LAN78XX_INT_STS_EEE_TX_LPI_STRT_EN  = $04000000;
 LAN78XX_INT_STS_EEE_TX_LPI_STOP_EN  = $02000000;
 LAN78XX_INT_STS_EEE_RX_LPI_EN  = $01000000;
 LAN78XX_INT_EP_RDFO_EN   = $00400000;
 LAN78XX_INT_EP_TXE_EN   = $00200000;
 LAN78XX_INT_EP_TX_DIS_EN  = $00080000;
 LAN78XX_INT_EP_RX_DIS_EN  = $00040000;
 LAN78XX_INT_EP_PHY_INT_EN  = $00020000;
 LAN78XX_INT_EP_DP_INT_EN  = $00010000;
 LAN78XX_INT_EP_MAC_ERR_EN  = $00008000;
 LAN78XX_INT_EP_TDFU_EN   = $00004000;
 LAN78XX_INT_EP_TDFO_EN   = $00002000;
 LAN78XX_INT_EP_UTX_FP_EN  = $00001000;
 LAN78XX_INT_EP_GPIO_EN_MASK  = $00000FFF;

 {PIPE CONTROL REGISTER (PIPE_CTL)}
 LAN78XX_PIPE_CTL   = $09C;
 LAN78XX_PIPE_CTL_TXSWING  = $00000040;
 LAN78XX_PIPE_CTL_TXMARGIN_MASK  = $00000038;
 LAN78XX_PIPE_CTL_TXDEEMPHASIS_MASK  = $00000006;
 LAN78XX_PIPE_CTL_ELASTICITYBUFFERMODE  = $00000001;

 {U1 EXIT LATENCY REGISTER (U1_LATENCY)}
 LAN78XX_U1_LATENCY   = $A0;
 
 {U2 EXIT LATENCY REGISTER (U2_LATENCY)}
 LAN78XX_U2_LATENCY   = $A4;

 {USB STATUS REGISTER (USB_STATUS)}
 LAN78XX_USB_STATUS   = $0A8;
 LAN78XX_USB_STATUS_REMOTE_WK  = $00100000;
 LAN78XX_USB_STATUS_FUNC_REMOTE_WK  = $00080000;
 LAN78XX_USB_STATUS_LTM_ENABLE  = $00040000;
 LAN78XX_USB_STATUS_U2_ENABLE  = $00020000;
 LAN78XX_USB_STATUS_U1_ENABLE  = $00010000;
 LAN78XX_USB_STATUS_SET_SEL  = $00000020;
 LAN78XX_USB_STATUS_REMOTE_WK_STS  = $00000010;
 LAN78XX_USB_STATUS_FUNC_REMOTE_WK_STS  = $00000008;
 LAN78XX_USB_STATUS_LTM_ENABLE_STS  = $00000004;
 LAN78XX_USB_STATUS_U2_ENABLE_STS  = $00000002;
 LAN78XX_USB_STATUS_U1_ENABLE_STS  = $00000001;

 {USB CONFIGURATION REGISTER 3 (USB_CFG3)}
 LAN78XX_USB_CFG3   = $0AC;
 LAN78XX_USB_CFG3_EN_U2_LTM  = $40000000;
 LAN78XX_USB_CFG3_BULK_OUT_NUMP_OVR  = $20000000;
 LAN78XX_USB_CFG3_DIS_FAST_U1_EXIT  = $10000000;
 LAN78XX_USB_CFG3_LPM_NYET_THR  = $0F000000;
 LAN78XX_USB_CFG3_RX_DET_2_POL_LFPS  = $00800000;
 LAN78XX_USB_CFG3_LFPS_FILT  = $00400000;
 LAN78XX_USB_CFG3_SKIP_RX_DET  = $00200000;
 LAN78XX_USB_CFG3_DELAY_P1P2P3  = $001C0000;
 LAN78XX_USB_CFG3_DELAY_PHY_PWR_CHG  = $00020000;
 LAN78XX_USB_CFG3_U1U2_EXIT_FR  = $00010000;
 LAN78XX_USB_CFG3_REQ_P1P2P3  = $00008000;
 LAN78XX_USB_CFG3_HST_PRT_CMPL  = $00004000;
 LAN78XX_USB_CFG3_DIS_SCRAMB  = $00002000;
 LAN78XX_USB_CFG3_PWR_DN_SCALE  = $00001FFF;

 {RECEIVE FILTERING ENGINE CONTROL REGISTER (RFE_CTL)}
 LAN78XX_RFE_CTL    = $0B0;
 LAN78XX_RFE_CTL_IGMP_COE  = $00004000;
 LAN78XX_RFE_CTL_ICMP_COE  = $00002000;
 LAN78XX_RFE_CTL_TCPUDP_COE  = $00001000;
 LAN78XX_RFE_CTL_IP_COE   = $00000800;
 LAN78XX_RFE_CTL_BCAST_EN  = $00000400;
 LAN78XX_RFE_CTL_MCAST_EN  = $00000200;
 LAN78XX_RFE_CTL_UCAST_EN  = $00000100;
 LAN78XX_RFE_CTL_VLAN_STRIP  = $00000080;
 LAN78XX_RFE_CTL_DISCARD_UNTAGGED  = $00000040;
 LAN78XX_RFE_CTL_VLAN_FILTER  = $00000020;
 LAN78XX_RFE_CTL_SA_FILTER  = $00000010;
 LAN78XX_RFE_CTL_MCAST_HASH  = $00000008;
 LAN78XX_RFE_CTL_DA_HASH  = $00000004;
 LAN78XX_RFE_CTL_DA_PERFECT  = $00000002;
 LAN78XX_RFE_CTL_RST   = $00000001;

 {VLAN TYPE REGISTER (VLAN_TYPE)}
 LAN78XX_VLAN_TYPE   = $0B4;
 LAN78XX_VLAN_TYPE_MASK   = $0000FFFF;

 {FIFO CONTROLLER RX FIFO CONTROL REGISTER (FCT_RX_CTL}
 LAN78XX_FCT_RX_CTL   = $0C0;
 LAN78XX_FCT_RX_CTL_EN   = $80000000;
 LAN78XX_FCT_RX_CTL_RST   = $40000000;
 LAN78XX_FCT_RX_CTL_SBF   = $02000000;
 LAN78XX_FCT_RX_CTL_OVFL  = $01000000;
 LAN78XX_FCT_RX_CTL_DROP  = $00800000;
 LAN78XX_FCT_RX_CTL_NOT_EMPTY  = $00400000;
 LAN78XX_FCT_RX_CTL_EMPTY  = $00200000;
 LAN78XX_FCT_RX_CTL_DIS   = $00100000;
 LAN78XX_FCT_RX_CTL_USED_MASK  = $0000FFFF;

 {FIFO CONTROLLER TX FIFO CONTROL REGISTER (FCT_TX_CTL)}
 LAN78XX_FCT_TX_CTL   = $0C4;
 LAN78XX_FCT_TX_CTL_EN   = $80000000;
 LAN78XX_FCT_TX_CTL_RST   = $40000000;
 LAN78XX_FCT_TX_CTL_NOT_EMPTY  = $00400000;
 LAN78XX_FCT_TX_CTL_EMPTY  = $00200000;
 LAN78XX_FCT_TX_CTL_DIS   = $00100000;
 LAN78XX_FCT_TX_CTL_USED_MASK  = $0000FFFF;

 {FCT RX FIFO END REGISTER (FCT_RX_FIFO_END}
 LAN78XX_FCT_RX_FIFO_END   = $0C8;
 LAN78XX_FCT_RX_FIFO_END_MASK  = $0000007F;

 {FCT TX FIFO END REGISTER (FCT_TX_FIFO_END)}
 LAN78XX_FCT_TX_FIFO_END   = $0CC;
 LAN78XX_FCT_TX_FIFO_END_MASK  = $0000003F;

 {FCT FLOW CONTROL THRESHOLD REGISTER (FCT_FLOW)}
 LAN78XX_FCT_FLOW   = $0D0;
 LAN78XX_FCT_FLOW_OFF_MASK  = $00007F00;
 LAN78XX_FCT_FLOW_ON_MASK  = $0000007F;

 {RX DATAPATH STORAGE (RX_DP_STOR)} 
 LAN78XX_RX_DP_STOR   = $0D4;
 LAN78XX_RX_DP_STORE_TOT_RXUSED_MASK  = $FFFF0000;
 LAN78XX_RX_DP_STORE_UTX_RXUSED_MASK  = $0000FFFF;

 {TX DATAPATH STORAGE (TX_DP_STOR)}
 LAN78XX_TX_DP_STOR   = $0D8;
 LAN78XX_TX_DP_STORE_TOT_TXUSED_MASK  = $FFFF0000;
 LAN78XX_TX_DP_STORE_URX_TXUSED_MASK  = $0000FFFF;

 {LTM BELT IDLE REGISTER 0 (LTM_BELT_IDLE0)}
 LAN78XX_LTM_BELT_IDLE0   = $0E0;
 LAN78XX_LTM_BELT_IDLE0_IDLE1000  = $0FFF0000;
 LAN78XX_LTM_BELT_IDLE0_IDLE100  = $00000FFF;

 {LTM BELT IDLE REGISTER 1 (LTM_BELT_IDLE1)}
 LAN78XX_LTM_BELT_IDLE1   = $0E4;
 LAN78XX_LTM_BELT_IDLE1_IDLE10  = $00000FFF;

 {LTM BELT ACTIVE REGISTER 0 (LTM_BELT_ACT0)}
 LAN78XX_LTM_BELT_ACT0   = $0E8;
 LAN78XX_LTM_BELT_ACT0_ACT1000  = $0FFF0000;
 LAN78XX_LTM_BELT_ACT0_ACT100  = $00000FFF;

 {LTM BELT ACTIVE REGISTER 1 (LTM_BELT_ACT1)}
 LAN78XX_LTM_BELT_ACT1   = $0EC;
 LAN78XX_LTM_BELT_ACT1_ACT10  = $00000FFF;

 {LTM INACTIVITY TIMER REGISTER (LTM_INACTIVE0)}
 LAN78XX_LTM_INACTIVE0   = $0F0;
 LAN78XX_LTM_INACTIVE0_TIMER1000  = $FFFF0000;
 LAN78XX_LTM_INACTIVE0_TIMER100  = $0000FFFF;

 {LTM INACTIVITY TIMER REGISTER (LTM_INACTIVE1)}
 LAN78XX_LTM_INACTIVE1   = $0F4;
 LAN78XX_LTM_INACTIVE1_TIMER10  = $0000FFFF;

 {MAC CONTROL REGISTER (MAC_CR)}
 LAN78XX_MAC_CR    = $100;
 LAN78XX_MAC_CR_EEE_TX_CLK_STOP_EN  = $00040000;
 LAN78XX_MAC_CR_EEE_EN   = $00020000;
 LAN78XX_MAC_CR_EEE_TLAR_EN  = $00010000;
 LAN78XX_MAC_CR_ADP   = $00002000;
 LAN78XX_MAC_CR_AUTO_DUPLEX  = $00001000;
 LAN78XX_MAC_CR_AUTO_SPEED  = $00000800;
 LAN78XX_MAC_CR_LOOPBACK  = $00000400;
 LAN78XX_MAC_CR_BOLMT_MASK  = $000000C0;
 LAN78XX_MAC_CR_FULL_DUPLEX  = $00000008;
 LAN78XX_MAC_CR_SPEED_MASK  = $00000006;
 LAN78XX_MAC_CR_SPEED_1000  = $00000004;
 LAN78XX_MAC_CR_SPEED_100  = $00000002;
 LAN78XX_MAC_CR_SPEED_10  = $00000000;
 LAN78XX_MAC_CR_RST   = $00000001;

 {MAC RECEIVE REGISTER (MAC_RX)}
 LAN78XX_MAC_RX    = $104;
 LAN78XX_MAC_RX_MAX_SIZE_SHIFT = 16;
 LAN78XX_MAC_RX_MAX_SIZE_MASK  = LongWord($3FFF0000);
 LAN78XX_MAC_RX_FCS_STRIP  = $00000010;
 LAN78XX_MAC_RX_VLAN_FSE  = $00000004;
 LAN78XX_MAC_RX_RXD   = $00000002;
 LAN78XX_MAC_RX_RXEN   = $00000001;

 {MAC TRANSMIT REGISTER (MAC_TX)}
 LAN78XX_MAC_TX    = $108;
 LAN78XX_MAC_TX_BAD_FCS   = $00000004;
 LAN78XX_MAC_TX_TXD   = $00000002;
 LAN78XX_MAC_TX_TXEN   = $00000001;

 {FLOW CONTROL REGISTER (FLOW)}
 LAN78XX_FLOW    = $10C;
 LAN78XX_FLOW_CR_FORCE_FC  = $80000000;
 LAN78XX_FLOW_CR_TX_FCEN  = $40000000;
 LAN78XX_FLOW_CR_RX_FCEN  = $20000000;
 LAN78XX_FLOW_CR_FPF   = $10000000;
 LAN78XX_FLOW_CR_FCPT_MASK  = $0000FFFF;

 {RANDOM NUMBER SEED VALUE REGISTER (RAND_SEED)}
 LAN78XX_RAND_SEED   = $110;
 LAN78XX_RAND_SEED_MASK   = $0000FFFF;

 {ERROR STATUS REGISTER (ERR_STS)}
 LAN78XX_ERR_STS    = $114;
 LAN78XX_ERR_STS_FERR   = $00000100;
 LAN78XX_ERR_STS_LERR   = $00000080;
 LAN78XX_ERR_STS_RFERR   = $00000040;
 LAN78XX_ERR_STS_ECERR   = $00000010;
 LAN78XX_ERR_STS_ALERR   = $00000008;
 LAN78XX_ERR_STS_URERR   = $00000004;

 {MAC RECEIVE ADDRESS HIGH REGISTER (RX_ADDRH)}
 LAN78XX_RX_ADDRH   = $118;
 LAN78XX_RX_ADDRH_MASK   = $0000FFFF;

 {MAC RECEIVE ADDRESS LOW REGISTER (RX_ADDRL)}
 LAN78XX_RX_ADDRL   = $11C;
 LAN78XX_RX_ADDRL_MASK   = $FFFFFFFF;

 {MII ACCESS REGISTER (MII_ACCESS)}
 LAN78XX_MII_ACC    = $120;
 LAN78XX_MII_ACC_PHY_ADDR_SHIFT = 11;
 LAN78XX_MII_ACC_PHY_ADDR_MASK  = $0000F800;
 LAN78XX_MII_ACC_MIIRINDA_SHIFT = 6;
 LAN78XX_MII_ACC_MIIRINDA_MASK  = $000007C0;
 LAN78XX_MII_ACC_MII_READ  = $00000000;
 LAN78XX_MII_ACC_MII_WRITE  = $00000002;
 LAN78XX_MII_ACC_MII_BUSY  = $00000001;

 {MII DATA REGISTER (MII_DATA)}
 LAN78XX_MII_DATA   = $124;
 LAN78XX_MII_DATA_MASK   = $0000FFFF;

 {UNKNOWN (MAC_RGMII_ID)}
 LAN78XX_MAC_RGMII_ID   = $128;
 LAN78XX_MAC_RGMII_ID_TXC_DELAY_EN  = $00000002;
 LAN78XX_MAC_RGMII_ID_RXC_DELAY_EN  = $00000001;

 {EEE TX LPI REQUEST DELAY COUNT REGISTER (EEE_TX_LPI_REQUEST_DELAY_CNT)}
 LAN78XX_EEE_TX_LPI_REQ_DLY  = $130;
 LAN78XX_EEE_TX_LPI_REQ_DLY_CNT_MASK  = $FFFFFFFF;

 {EEE TIME WAIT TX SYSTEM REGISTER (EEE_TW_TX_SYS)}
 LAN78XX_EEE_TW_TX_SYS   = $134;
 LAN78XX_EEE_TW_TX_SYS_CNT1G_MASK  = $FFFF0000;
 LAN78XX_EEE_TW_TX_SYS_CNT100M_MASK  = $0000FFFF;

 {EEE TX LPI AUTOMATIC REMOVAL DELAY REGISTER (EEE_TX_LPI_AUTO_REMOVAL_DELAY)}
 LAN78XX_EEE_TX_LPI_REM_DLY  = $138;
 LAN78XX_EEE_TX_LPI_REM_DLY_CNT  = $00FFFFFF;

 {WAKEUP CONTROL AND STATUS REGISTER 1 (WUCSR1)}
 LAN78XX_WUCSR    = $140;
 LAN78XX_WUCSR_TESTMODE   = $80000000;
 LAN78XX_WUCSR_RFE_WAKE_EN  = $00004000;
 LAN78XX_WUCSR_EEE_TX_WAKE  = $00002000;
 LAN78XX_WUCSR_EEE_TX_WAKE_EN  = $00001000;
 LAN78XX_WUCSR_EEE_RX_WAKE  = $00000800;
 LAN78XX_WUCSR_EEE_RX_WAKE_EN  = $00000400;
 LAN78XX_WUCSR_RFE_WAKE_FR  = $00000200;
 LAN78XX_WUCSR_STORE_WAKE  = $00000100;
 LAN78XX_WUCSR_PFDA_FR   = $00000080;
 LAN78XX_WUCSR_WUFR   = $00000040;
 LAN78XX_WUCSR_MPR   = $00000020;
 LAN78XX_WUCSR_BCST_FR   = $00000010;
 LAN78XX_WUCSR_PFDA_EN   = $00000008;
 LAN78XX_WUCSR_WAKE_EN   = $00000004;
 LAN78XX_WUCSR_MPEN   = $00000002;
 LAN78XX_WUCSR_BCST_EN   = $00000001;

 {WAKEUP SOURCE REGISTER (WK_SRC)}
 LAN78XX_WK_SRC    = $144;
 LAN78XX_WK_SRC_GPIOX_INT_WK_SHIFT = 20;
 LAN78XX_WK_SRC_GPIOX_INT_WK_MASK  = $FFF00000;
 LAN78XX_WK_SRC_IPV6_TCPSYN_RCD_WK  = $00010000;
 LAN78XX_WK_SRC_IPV4_TCPSYN_RCD_WK  = $00008000;
 LAN78XX_WK_SRC_EEE_TX_WK  = $00004000;
 LAN78XX_WK_SRC_EEE_RX_WK  = $00002000;
 LAN78XX_WK_SRC_GOOD_FR_WK  = $00001000;
 LAN78XX_WK_SRC_PFDA_FR_WK  = $00000800;
 LAN78XX_WK_SRC_MP_FR_WK  = $00000400;
 LAN78XX_WK_SRC_BCAST_FR_WK  = $00000200;
 LAN78XX_WK_SRC_WU_FR_WK  = $00000100;
 LAN78XX_WK_SRC_WUFF_MATCH_MASK  = $0000001F;

 {WAKEUP FILTER X CONFIGURATION REGISTER (WUF_CFGX)}
 LAN78XX_WUF_CFG0   = $150;
 LAN78XX_NUM_OF_WUF_CFG  = 32;
 LAN78XX_WUF_CFG_BEGIN  = LAN78XX_WUF_CFG0;
 {LAN78XX_WUF_CFG(index)  = LAN78XX_WUF_CFG_BEGIN + (4 * (index)))}
 LAN78XX_WUF_CFGX_EN   = $80000000;
 LAN78XX_WUF_CFGX_TYPE_MASK  = $03000000;
 LAN78XX_WUF_CFGX_TYPE_MCAST  = $02000000;
 LAN78XX_WUF_CFGX_TYPE_ALL  = $01000000;
 LAN78XX_WUF_CFGX_TYPE_UCAST  = $00000000;
 LAN78XX_WUF_CFGX_OFFSET_SHIFT = 16;
 LAN78XX_WUF_CFGX_OFFSET_MASK  = $00FF0000;
 LAN78XX_WUF_CFGX_CRC16_MASK  = $0000FFFF;

 {WAKEUP FILTER X BYTE MASK REGISTERS (WUF_MASKX}
 LAN78XX_WUF_MASK0_0   = $200;
 LAN78XX_WUF_MASK0_1   = $204;
 LAN78XX_WUF_MASK0_2   = $208;
 LAN78XX_WUF_MASK0_3   = $20C;
 LAN78XX_NUM_OF_WUF_MASK  = 32;
 LAN78XX_WUF_MASK0_BEGIN  = LAN78XX_WUF_MASK0_0;
 LAN78XX_WUF_MASK1_BEGIN  = LAN78XX_WUF_MASK0_1;
 LAN78XX_WUF_MASK2_BEGIN  = LAN78XX_WUF_MASK0_2;
 LAN78XX_WUF_MASK3_BEGIN  = LAN78XX_WUF_MASK0_3;
 {LAN78XX_WUF_MASK0(index) = LAN78XX_WUF_MASK0_BEGIN + = $10 * (index)))}
 {LAN78XX_WUF_MASK1(index) = LAN78XX_WUF_MASK1_BEGIN + = $10 * (index)))}
 {LAN78XX_WUF_MASK2(index) = LAN78XX_WUF_MASK2_BEGIN + = $10 * (index)))}
 {LAN78XX_WUF_MASK3(index) = LAN78XX_WUF_MASK3_BEGIN + = $10 * (index)))}

 {MAC ADDRESS PERFECT FILTER REGISTERS (ADDR_FILTX)}
 {0x400 - 0x504}
 LAN78XX_MAF_BASE   = $400;
 LAN78XX_MAF_HIX    = $00;
 LAN78XX_MAF_LOX    = $04;
 LAN78XX_NUM_OF_MAF  = 33;
 LAN78XX_MAF_HI_BEGIN  = LAN78XX_MAF_BASE + LAN78XX_MAF_HIX;
 LAN78XX_MAF_LO_BEGIN  = LAN78XX_MAF_BASE + LAN78XX_MAF_LOX;
 {LAN78XX_MAF_HI(index)  = LAN78XX_MAF_BASE + (8 * (index)) + (LAN78XX_MAF_HIX))}
 {LAN78XX_MAF_LO(index)  = LAN78XX_MAF_BASE + (8 * (index)) + (LAN78XX_MAF_LOX))}
 LAN78XX_MAF_HI_VALID   = $80000000;
 LAN78XX_MAF_HI_TYPE_MASK  = $40000000;
 LAN78XX_MAF_HI_TYPE_SRC  = $40000000;
 LAN78XX_MAF_HI_TYPE_DST  = $00000000;
 LAN78XX_MAF_HI_ADDR_MASK  = $0000FFFF;
 LAN78XX_MAF_LO_ADDR_MASK  = $FFFFFFFF;

 {WAKEUP CONTROL AND STATUS REGISTER 2 (WUCSR2}
 LAN78XX_WUCSR2    = $600;
 LAN78XX_WUCSR2_CSUM_DISABLE  = $80000000;
 LAN78XX_WUCSR2_NA_SA_SEL  = $00000100;
 LAN78XX_WUCSR2_NS_RCD   = $00000080;
 LAN78XX_WUCSR2_ARP_RCD   = $00000040;
 LAN78XX_WUCSR2_IPV6_TCPSYN_RCD  = $00000020;
 LAN78XX_WUCSR2_IPV4_TCPSYN_RCD  = $00000010;
 LAN78XX_WUCSR2_NS_OFFLOAD_EN  = $00000008;
 LAN78XX_WUCSR2_ARP_OFFLOAD_EN  = $00000004;
 LAN78XX_WUCSR2_IPV6_TCPSYN_WAKE_EN  = $00000002;
 LAN78XX_WUCSR2_IPV4_TCPSYN_WAKE_EN  = $00000001;

 {NSX IPV6 DESTINATION ADDRESS REGISTER (NSX_IPV6_ADDR_DEST)}
 {0x610h - 0x61C}
 {0x650h - 0x65C}
 LAN78XX_NS1_IPV6_ADDR_DEST0  = $610;
 LAN78XX_NS1_IPV6_ADDR_DEST1  = $614;
 LAN78XX_NS1_IPV6_ADDR_DEST2  = $618;
 LAN78XX_NS1_IPV6_ADDR_DEST3  = $61C;

 {NSX IPV6 SOURCE ADDRESS REGISTER (NSX_IPV6_ADDR_SRC}
 {0x620 - 0x62C}
 {0x660 - 0x66C}
 LAN78XX_NS1_IPV6_ADDR_SRC0  = $620;
 LAN78XX_NS1_IPV6_ADDR_SRC1  = $624;
 LAN78XX_NS1_IPV6_ADDR_SRC2  = $628;
 LAN78XX_NS1_IPV6_ADDR_SRC3  = $62C;

 {NSX ICMPV6 ADDRESS 0 REGISTER (NSX_ICMPV6_ADDR0)}
 {0x630 - 0x63C}
 {0x670 - 0x67C}
 LAN78XX_NS1_ICMPV6_ADDR0_0  = $630;
 LAN78XX_NS1_ICMPV6_ADDR0_1  = $634;
 LAN78XX_NS1_ICMPV6_ADDR0_2  = $638;
 LAN78XX_NS1_ICMPV6_ADDR0_3  = $63C;

 {NSX ICMPV6 ADDRESS 1 REGISTER (NSX_ICMPV6_ADDR1)}
 {0x640 - 0x64C}
 {0x680 - 0x68C}
 LAN78XX_NS1_ICMPV6_ADDR1_0  = $640;
 LAN78XX_NS1_ICMPV6_ADDR1_1  = $644;
 LAN78XX_NS1_ICMPV6_ADDR1_2  = $648;
 LAN78XX_NS1_ICMPV6_ADDR1_3  = $64C;

 {NSX IPV6 DESTINATION ADDRESS REGISTER (NSX_IPV6_ADDR_DEST)}
 LAN78XX_NS2_IPV6_ADDR_DEST0  = $650;
 LAN78XX_NS2_IPV6_ADDR_DEST1  = $654;
 LAN78XX_NS2_IPV6_ADDR_DEST2  = $658;
 LAN78XX_NS2_IPV6_ADDR_DEST3  = $65C;

 {NSX IPV6 SOURCE ADDRESS REGISTER (NSX_IPV6_ADDR_SRC}
 LAN78XX_NS2_IPV6_ADDR_SRC0  = $660;
 LAN78XX_NS2_IPV6_ADDR_SRC1  = $664;
 LAN78XX_NS2_IPV6_ADDR_SRC2  = $668;
 LAN78XX_NS2_IPV6_ADDR_SRC3  = $66C;

 {NSX ICMPV6 ADDRESS 0 REGISTER (NSX_ICMPV6_ADDR0)}
 LAN78XX_NS2_ICMPV6_ADDR0_0  = $670;
 LAN78XX_NS2_ICMPV6_ADDR0_1  = $674;
 LAN78XX_NS2_ICMPV6_ADDR0_2  = $678;
 LAN78XX_NS2_ICMPV6_ADDR0_3  = $67C;

 {NSX ICMPV6 ADDRESS 1 REGISTER (NSX_ICMPV6_ADDR1)}
 LAN78XX_NS2_ICMPV6_ADDR1_0  = $680;
 LAN78XX_NS2_ICMPV6_ADDR1_1  = $684;
 LAN78XX_NS2_ICMPV6_ADDR1_2  = $688;
 LAN78XX_NS2_ICMPV6_ADDR1_3  = $68C;

 {SYN IPV4 SOURCE ADDRESS REGISTER (SYN_IPV4_ADDR_SRC}
 LAN78XX_SYN_IPV4_ADDR_SRC  = $690;
 
 {SYN IPV4 DESTINATION ADDRESS REGISTER (SYN_IPV4_ADDR_DEST)}
 LAN78XX_SYN_IPV4_ADDR_DEST  = $694;

 {SYN IPV4 TCP PORTS REGISTER (SYN_IPV4_TCP_PORTS}
 LAN78XX_SYN_IPV4_TCP_PORTS  = $698;
 LAN78XX_SYN_IPV4_TCP_PORTS_IPV4_DEST_PORT_SHIFT   = 16;
 LAN78XX_SYN_IPV4_TCP_PORTS_IPV4_DEST_PORT_MASK     = $FFFF0000;
 LAN78XX_SYN_IPV4_TCP_PORTS_IPV4_SRC_PORT_MASK     = $0000FFFF;

 {SYN IPV6 SOURCE ADDRESS REGISTER (SYN_IPV6_ADDR_SRC)}
 LAN78XX_SYN_IPV6_ADDR_SRC0  = $69C;
 LAN78XX_SYN_IPV6_ADDR_SRC1  = $6A0;
 LAN78XX_SYN_IPV6_ADDR_SRC2  = $6A4;
 LAN78XX_SYN_IPV6_ADDR_SRC3  = $6A8;

 {SYN IPV6 DESTINATION ADDRESS REGISTER (SYN_IPV6_ADDR_DEST)}
 LAN78XX_SYN_IPV6_ADDR_DEST0  = $6AC;
 LAN78XX_SYN_IPV6_ADDR_DEST1  = $6B0;
 LAN78XX_SYN_IPV6_ADDR_DEST2  = $6B4;
 LAN78XX_SYN_IPV6_ADDR_DEST3  = $6B8;

 {SYN IPV6 TCP PORTS REGISTER (SYN_IPV6_TCP_PORTS)}
 LAN78XX_SYN_IPV6_TCP_PORTS  = $6BC;
 LAN78XX_SYN_IPV6_TCP_PORTS_IPV6_DEST_PORT_SHIFT   = 16;
 LAN78XX_SYN_IPV6_TCP_PORTS_IPV6_DEST_PORT_MASK     = $FFFF0000;
 LAN78XX_SYN_IPV6_TCP_PORTS_IPV6_SRC_PORT_MASK     = $0000FFFF;

 {ARP SENDER PROTOCOL ADDRESS REGISTER (ARP_SPA)}
 LAN78XX_ARP_SPA    = $6C0;
 
 {ARP TARGET PROTOCOL ADDRESS REGISTER (ARP_TPA)}
 LAN78XX_ARP_TPA    = $6C4;

 {PHY DEVICE IDENTIFIER (PHY_DEV_ID)}
 LAN78XX_PHY_DEV_ID   = $700;
 LAN78XX_PHY_DEV_ID_REV_SHIFT = 28;
 LAN78XX_PHY_DEV_ID_REV_MASK  = $F0000000;
 LAN78XX_PHY_DEV_ID_MODEL_SHIFT = 22;
 LAN78XX_PHY_DEV_ID_MODEL_MASK  = $0FC00000;
 LAN78XX_PHY_DEV_ID_OUI_MASK  = $003FFFFF;

 {OTP}
 LAN78XX_OTP_BASE_ADDR   = $00001000;
 LAN78XX_OTP_ADDR_RANGE   = $1FF;

 LAN78XX_OTP_PWR_DN    = LAN78XX_OTP_BASE_ADDR + 4 * $00;
 LAN78XX_OTP_PWR_DN_PWRDN_N  = $01;

 LAN78XX_OTP_ADDR1    = LAN78XX_OTP_BASE_ADDR + 4 * $01;
 LAN78XX_OTP_ADDR1_15_11   = $1F;

 LAN78XX_OTP_ADDR2    = LAN78XX_OTP_BASE_ADDR + 4 * $02;
 LAN78XX_OTP_ADDR2_10_3   = $FF;

 LAN78XX_OTP_ADDR3    = LAN78XX_OTP_BASE_ADDR + 4 * $03;
 LAN78XX_OTP_ADDR3_2_0   = $03;

 LAN78XX_OTP_PRGM_DATA    = LAN78XX_OTP_BASE_ADDR + 4 * $04;

 LAN78XX_OTP_PRGM_MODE    = LAN78XX_OTP_BASE_ADDR + 4 * $05;
 LAN78XX_OTP_PRGM_MODE_BYTE  = $01;

 LAN78XX_OTP_RD_DATA    = LAN78XX_OTP_BASE_ADDR + 4 * $06;

 LAN78XX_OTP_FUNC_CMD    = LAN78XX_OTP_BASE_ADDR + 4 * $08;
 LAN78XX_OTP_FUNC_CMD_RESET  = $04;
 LAN78XX_OTP_FUNC_CMD_PROGRAM  = $02;
 LAN78XX_OTP_FUNC_CMD_READ  = $01;

 LAN78XX_OTP_TST_CMD    = LAN78XX_OTP_BASE_ADDR + 4 * $09;
 LAN78XX_OTP_TST_CMD_TEST_DEC_SEL  = $10;
 LAN78XX_OTP_TST_CMD_PRGVRFY  = $08;
 LAN78XX_OTP_TST_CMD_WRTEST  = $04;
 LAN78XX_OTP_TST_CMD_TESTDEC  = $02;
 LAN78XX_OTP_TST_CMD_BLANKCHECK  = $01;

 LAN78XX_OTP_CMD_GO    = LAN78XX_OTP_BASE_ADDR + 4 * $0A;
 LAN78XX_OTP_CMD_GO_GO   = $01;

 LAN78XX_OTP_PASS_FAIL    = LAN78XX_OTP_BASE_ADDR + 4 * $0B;
 LAN78XX_OTP_PASS_FAIL_PASS  = $02;
 LAN78XX_OTP_PASS_FAIL_FAIL  = $01;

 LAN78XX_OTP_STATUS    = LAN78XX_OTP_BASE_ADDR + 4 * $0C;
 LAN78XX_OTP_STATUS_OTP_LOCK  = $10;
 LAN78XX_OTP_STATUS_WEB   = $08;
 LAN78XX_OTP_STATUS_PGMEN  = $04;
 LAN78XX_OTP_STATUS_CPUMPEN  = $02;
 LAN78XX_OTP_STATUS_BUSY  = $01;

 LAN78XX_OTP_MAX_PRG    = LAN78XX_OTP_BASE_ADDR + 4 * $0D;
 LAN78XX_OTP_MAX_PRG_MAX_PROG  = $1F;

 LAN78XX_OTP_INTR_STATUS    = LAN78XX_OTP_BASE_ADDR + 4 * $10;
 LAN78XX_OTP_INTR_STATUS_READY  = $01;

 LAN78XX_OTP_INTR_MASK    = LAN78XX_OTP_BASE_ADDR + 4 * $11;
 LAN78XX_OTP_INTR_MASK_READY  = $01;

 LAN78XX_OTP_RSTB_PW1    = LAN78XX_OTP_BASE_ADDR + 4 * $14;
 LAN78XX_OTP_RSTB_PW2    = LAN78XX_OTP_BASE_ADDR + 4 * $15;
 LAN78XX_OTP_PGM_PW1    = LAN78XX_OTP_BASE_ADDR + 4 * $18;
 LAN78XX_OTP_PGM_PW2    = LAN78XX_OTP_BASE_ADDR + 4 * $19;
 LAN78XX_OTP_READ_PW1    = LAN78XX_OTP_BASE_ADDR + 4 * $1C;
 LAN78XX_OTP_READ_PW2    = LAN78XX_OTP_BASE_ADDR + 4 * $1D;
 LAN78XX_OTP_TCRST    = LAN78XX_OTP_BASE_ADDR + 4 * $20;
 LAN78XX_OTP_RSRD    = LAN78XX_OTP_BASE_ADDR + 4 * $21;
 LAN78XX_OTP_TREADEN_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $22;
 LAN78XX_OTP_TDLES_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $23;
 LAN78XX_OTP_TWWL_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $24;
 LAN78XX_OTP_TDLEH_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $25;
 LAN78XX_OTP_TWPED_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $26;
 LAN78XX_OTP_TPES_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $27;
 LAN78XX_OTP_TCPS_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $28;
 LAN78XX_OTP_TCPH_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $29;
 LAN78XX_OTP_TPGMVFY_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $2A;
 LAN78XX_OTP_TPEH_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $2B;
 LAN78XX_OTP_TPGRST_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $2C;
 LAN78XX_OTP_TCLES_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $2D;
 LAN78XX_OTP_TCLEH_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $2E;
 LAN78XX_OTP_TRDES_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $2F;
 LAN78XX_OTP_TBCACC_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $30;
 LAN78XX_OTP_TAAC_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $31;
 LAN78XX_OTP_TACCT_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $32;
 LAN78XX_OTP_TRDEP_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $38;
 LAN78XX_OTP_TPGSV_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $39;
 LAN78XX_OTP_TPVSR_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $3A;
 LAN78XX_OTP_TPVHR_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $3B;
 LAN78XX_OTP_TPVSA_VAL    = LAN78XX_OTP_BASE_ADDR + 4 * $3C;
 
 {LAN78XX/LAN88XX PHY}
 LAN88XX_INT_MASK   = $19;
 LAN88XX_INT_MASK_MDINTPIN_EN  = $8000;
 LAN88XX_INT_MASK_SPEED_CHANGE  = $4000;
 LAN88XX_INT_MASK_LINK_CHANGE  = $2000;
 LAN88XX_INT_MASK_FDX_CHANGE  = $1000;
 LAN88XX_INT_MASK_AUTONEG_ERR  = $0800;
 LAN88XX_INT_MASK_AUTONEG_DONE  = $0400;
 LAN88XX_INT_MASK_POE_DETECT  = $0200;
 LAN88XX_INT_MASK_SYMBOL_ERR  = $0100;
 LAN88XX_INT_MASK_FAST_LINK_FAIL = $0080;
 LAN88XX_INT_MASK_WOL_EVENT  = $0040;
 LAN88XX_INT_MASK_EXTENDED_INT  = $0020;
 LAN88XX_INT_MASK_RESERVED  = $0010;
 LAN88XX_INT_MASK_FALSE_CARRIER  = $0008;
 LAN88XX_INT_MASK_LINK_SPEED_DS  = $0004;
 LAN88XX_INT_MASK_MASTER_SLAVE_DONE = $0002;
 LAN88XX_INT_MASK_RX__ER  = $0001;

 LAN88XX_INT_STS    = $1A;
 LAN88XX_INT_STS_INT_ACTIVE  = $8000;
 LAN88XX_INT_STS_SPEED_CHANGE  = $4000;
 LAN88XX_INT_STS_LINK_CHANGE  = $2000;
 LAN88XX_INT_STS_FDX_CHANGE  = $1000;
 LAN88XX_INT_STS_AUTONEG_ERR  = $0800;
 LAN88XX_INT_STS_AUTONEG_DONE  = $0400;
 LAN88XX_INT_STS_POE_DETECT  = $0200;
 LAN88XX_INT_STS_SYMBOL_ERR  = $0100;
 LAN88XX_INT_STS_FAST_LINK_FAIL  = $0080;
 LAN88XX_INT_STS_WOL_EVENT  = $0040;
 LAN88XX_INT_STS_EXTENDED_INT  = $0020;
 LAN88XX_INT_STS_RESERVED  = $0010;
 LAN88XX_INT_STS_FALSE_CARRIER  = $0008;
 LAN88XX_INT_STS_LINK_SPEED_DS  = $0004;
 LAN88XX_INT_STS_MASTER_SLAVE_DONE = $0002;
 LAN88XX_INT_STS_RX_ER   = $0001;

 LAN88XX_EXT_PAGE_ACCESS   = $1F;
 LAN88XX_EXT_PAGE_SPACE_0  = $0000;
 LAN88XX_EXT_PAGE_SPACE_1  = $0001;
 LAN88XX_EXT_PAGE_SPACE_2  = $0002;

 {Extended Register Page 1 space}
 LAN88XX_EXT_MODE_CTRL   = $13;
 LAN88XX_EXT_MODE_CTRL_MDIX_MASK = $000C;
 LAN88XX_EXT_MODE_CTRL_AUTO_MDIX = $0000;
 LAN88XX_EXT_MODE_CTRL_MDI  = $0008;
 LAN88XX_EXT_MODE_CTRL_MDI_X  = $000C;

 {MMD 3 Registers}
 LAN88XX_MMD3_CHIP_ID = 32877;
 LAN88XX_MMD3_CHIP_REV = 32878;
 
{==============================================================================}
type
 {LAN78XX specific types}
 PLAN78XXNetwork = ^TLAN78XXNetwork;
 TLAN78XXNetwork = record
  {Network Properties}
  Network:TNetworkDevice;
  {Driver Properties}
  ChipID:LongWord;
  ChipRevision:LongWord;
  PHYLock:TMutexHandle;
  ReceiveRequestSize:LongWord;                  {Size of each USB receive request buffer}
  TransmitRequestSize:LongWord;                 {Size of each USB transmit request buffer}
  ReceiveEntryCount:LongWord;                   {Number of entries in the receive queue}
  TransmitEntryCount:LongWord;                  {Number of entries in the transmit queue}
  ReceivePacketCount:LongWord;                  {Maximum number of packets per receive entry}
  TransmitPacketCount:LongWord;                 {Maximum number of packets per transmit entry}
  {USB Properties}
  ReceiveRequest:PUSBRequest;                   {USB request for packet receive data}
  TransmitRequest:PUSBRequest;                  {USB request for packet transmit data}
  InterruptRequest:PUSBRequest;                 {USB request for interrupt data}
  ReceiveEndpoint:PUSBEndpointDescriptor;       {Bulk IN Endpoint}
  TransmitEndpoint:PUSBEndpointDescriptor;      {Bulk OUT Endpoint}
  InterruptEndpoint:PUSBEndpointDescriptor;     {Interrupt IN Endpoint}
  PendingCount:LongWord;                        {Number of USB requests pending for this network}
  WaiterThread:TThreadId;                       {Thread waiting for pending requests to complete (for network close)}
 end; 
 
 PLAN78XXStatistics = ^TLAN78XXStatistics;
 TLAN78XXStatistics = record
  RXFCSErrors:LongWord;
  RXAlignmentErrors:LongWord;
  RXFragmentErrors:LongWord;
  RXJabberErrors:LongWord;
  RXUndersizeFrameErrors:LongWord;
  RXOversizeFrameErrors:LongWord;
  RXDroppedFrames:LongWord;
  RXUnicastByteCount:LongWord;
  RXBroadcastByteCount:LongWord;
  RXMulticastByteCount:LongWord;
  RXUnicastFrames:LongWord;
  RXBroadcastFrames:LongWord;
  RXMulticastFrames:LongWord;
  RXPauseFrames:LongWord;
  RX64ByteFrames:LongWord;
  RX65_127ByteFrames:LongWord;
  RX128_255ByteFrames:LongWord;
  RX256_511BytesFrames:LongWord;
  RX512_1023ByteFrames:LongWord;
  RX1024_1518ByteFrames:LongWord;
  RXGreater_1518ByteFrames:LongWord;
  EEERXLPITransitions:LongWord;
  EEERXLPITime:LongWord;
  TXFCSErrors:LongWord;
  TXExcessDeferralErrors:LongWord;
  TXCarrierErrors:LongWord;
  TXBadByteCount:LongWord;
  TXSingleCollisions:LongWord;
  TXMultipleCollisions:LongWord;
  TXExcessive_collision:LongWord;
  TXLateCollisions:LongWord;
  TXUnicastByteCount:LongWord;
  TXBroadcastByteCount:LongWord;
  TXMulticastByteCount:LongWord;
  TXUnicastFrames:LongWord;
  TXBroadcastFrames:LongWord;
  TXMulticastFrames:LongWord;
  TXPauseFrames:LongWord;
  TX64ByteFrames:LongWord;
  TX65_127ByteFrames:LongWord;
  TX128_255ByteFrames:LongWord;
  TX256_511BytesFrames:LongWord;
  TX512_1023ByteFrames:LongWord;
  TX1024_1518ByteFrames:LongWord;
  TXGreater_1518ByteFrames:LongWord;
  EEETXLPITransitions:LongWord;
  EEETXLPITime:LongWord;
 end;
 
{==============================================================================}
{var}
 {LAN78XX specific variables}
 
{==============================================================================}
{Initialization Functions}
procedure LAN78XXInit;
 
{==============================================================================}
{LAN78XX Network Functions}
function LAN78XXNetworkOpen(Network:PNetworkDevice):LongWord;
function LAN78XXNetworkClose(Network:PNetworkDevice):LongWord;
function LAN78XXNetworkControl(Network:PNetworkDevice;Request:Integer;Argument1:PtrUInt;var Argument2:PtrUInt):LongWord;

function LAN78XXBufferAllocate(Network:PNetworkDevice;var Entry:PNetworkEntry):LongWord;
function LAN78XXBufferRelease(Network:PNetworkDevice;Entry:PNetworkEntry):LongWord;
function LAN78XXBufferReceive(Network:PNetworkDevice;var Entry:PNetworkEntry):LongWord;
function LAN78XXBufferTransmit(Network:PNetworkDevice;Entry:PNetworkEntry):LongWord;

procedure LAN78XXTransmitStart(Network:PLAN78XXNetwork);

{==============================================================================}
{LAN78XX USB Functions}
function LAN78XXDriverBind(Device:PUSBDevice;Interrface:PUSBInterface):LongWord;
function LAN78XXDriverUnbind(Device:PUSBDevice;Interrface:PUSBInterface):LongWord;
 
procedure LAN78XXReceiveWorker(Request:PUSBRequest); 
procedure LAN78XXReceiveComplete(Request:PUSBRequest); 

procedure LAN78XXTransmitWorker(Request:PUSBRequest); 
procedure LAN78XXTransmitComplete(Request:PUSBRequest); 

procedure LAN78XXInterruptWorker(Request:PUSBRequest);
procedure LAN78XXInterruptComplete(Request:PUSBRequest);
 
{==============================================================================}
{LAN78XX Helper Functions}
function LAN78XXCheckDevice(Device:PUSBDevice):LongWord;

function LAN78XXReadRegister(Device:PUSBDevice;Index:LongWord;var Data:LongWord):LongWord;
function LAN78XXWriteRegister(Device:PUSBDevice;Index,Data:LongWord):LongWord;
function LAN78XXModifyRegister(Device:PUSBDevice;Index,Mask,Value:LongWord):LongWord;

function LAN78XXSetRegisterBits(Device:PUSBDevice;Index,Value:LongWord):LongWord;
function LAN78XXClearRegisterBits(Device:PUSBDevice;Index,Value:LongWord):LongWord;

function LAN78XXPHYRead(Device:PUSBDevice;Index:LongWord;var Value:Word):LongWord;
function LAN78XXPHYWrite(Device:PUSBDevice;Index:LongWord;Value:Word):LongWord;
function LAN78XXPHYInitialize(Device:PUSBDevice):LongWord;
function LAN78XXPHYWaitNotBusy(Device:PUSBDevice):LongWord;

function LAN78XXReadEEPROM(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;
function LAN78XXReadRawEEPROM(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;

function LAN78XXReadOTP(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;
function LAN78XXReadRawOTP(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;

function LAN78XXGetStatistics(Device:PUSBDevice;var Statistics:TLAN78XXStatistics):LongWord;

function LAN78XXGetMacAddress(Device:PUSBDevice;Address:PHardwareAddress):LongWord;
function LAN78XXSetMacAddress(Device:PUSBDevice;Address:PHardwareAddress):LongWord;
              
{==============================================================================}
{==============================================================================}

implementation

{==============================================================================}
{==============================================================================}
var
 {LAN78XX specific variables}
 LAN78XXInitialized:Boolean; 
 
 LAN78XXDriver:PUSBDriver;  {LAN78XX Driver interface (Set by LAN78XXInit)}

{==============================================================================}
{==============================================================================}
{Internal Functions}
function LAN78XX_MAF_HI(Index:LongWord):LongWord; inline;
{Get the MAC Address Filter High Register for the specified index}
begin
 {}
 Result:=LAN78XX_MAF_BASE + (8 * Index) + LAN78XX_MAF_HIX;
end;

{==============================================================================}

function LAN78XX_MAF_LO(Index:LongWord):LongWord; inline;
{Get the MAC Address Filter Low Register for the specified index}
begin
 {}
 Result:=LAN78XX_MAF_BASE + (8 * Index) + LAN78XX_MAF_LOX;
end;

{==============================================================================}
{==============================================================================}
{Initialization Functions}
procedure LAN78XXInit;
{Initialize the LAN78XX unit, create and register the driver}

{Note: Called only during system startup}
var
 Status:LongWord;
begin
 {}
 {Check Initialized}
 if LAN78XXInitialized then Exit;

 {Create LAN78XX Network Driver}
 LAN78XXDriver:=USBDriverCreate;
 if LAN78XXDriver <> nil then
  begin
   {Update LAN78XX Network Driver}
   {Driver}
   LAN78XXDriver.Driver.DriverName:=LAN78XX_DRIVER_NAME; 
   {USB}
   LAN78XXDriver.DriverBind:=LAN78XXDriverBind;
   LAN78XXDriver.DriverUnbind:=LAN78XXDriverUnbind;

   {Register LAN78XX Network Driver}
   Status:=USBDriverRegister(LAN78XXDriver);
   if Status <> USB_STATUS_SUCCESS then
    begin
     if USB_LOG_ENABLED then USBLogError(nil,'LAN78XX: Failed to register LAN78XX driver: ' + USBStatusToString(Status));
    end;
  end
 else
  begin
   if USB_LOG_ENABLED then USBLogError(nil,'LAN78XX: Failed to create LAN78XX driver');
  end;
 
 LAN78XXInitialized:=True;
end;
 
{==============================================================================}
{==============================================================================}
{LAN78XX Network Functions}
function LAN78XXNetworkOpen(Network:PNetworkDevice):LongWord;
{Implementation of NetworkDeviceOpen for the LAN78XX device}
{Note: Not intended to be called directly by applications, use NetworkDeviceOpen instead}
var
 Value:Word;
 Current:Int64;
 Status:LongWord;
 Buffer:LongWord;
 Device:PUSBDevice;
 Entry:PNetworkEntry;
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;

 {Check Network}
 if Network = nil then Exit;
 if Network.Device.Signature <> DEVICE_SIGNATURE then Exit;

 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX: Network Open');
 {$ENDIF}
 
 {Get Device}
 Device:=PUSBDevice(Network.Device.DeviceData);
 if Device = nil then Exit;
 
 {Acquire the Lock}
 if MutexLock(Network.Lock) = ERROR_SUCCESS then
  begin
   try
    {Check State}
    Result:=ERROR_ALREADY_OPEN;
    if Network.NetworkState <> NETWORK_STATE_CLOSED then Exit;

    {Set Result}
    Result:=ERROR_OPERATION_FAILED;
 
    {Reset Device}
    //To Do //lan78xx_reset
    
    {Set MAC Address}
    //To Do //lan78xx_init_mac_address (Remove from DriverBind)
    
    {Get Revision ID}
    Status:=LAN78XXReadRegister(Device,LAN78XX_ID_REV,Buffer);
    if Status <> USB_STATUS_SUCCESS then Exit;
    
    {Get Chip ID and Revision}
    PLAN78XXNetwork(Network).ChipID:=(Buffer and LAN78XX_ID_REV_CHIP_ID_MASK) shr 16;
    PLAN78XXNetwork(Network).ChipRevision:=Buffer and LAN78XX_ID_REV_CHIP_REV_MASK;
    {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
    if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: ID=' + IntToHex(PLAN78XXNetwork(Network).ChipID,4) + ' Revision=' + IntToHex(PLAN78XXNetwork(Network).ChipRevision,4));
    {$ENDIF}
    
    {Respond to Bulk In request with a NAK when the RX FIFO is empty}
    Status:=LAN78XXSetRegisterBits(Device,LAN78XX_USB_CFG0,LAN78XX_USB_CFG_BIR);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to enable bulk in request: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    //To Do //lan78xx_init_ltm
    
    {Check the Speed}
    if Device.Speed = USB_SPEED_SUPER then
     begin
      Buffer:=LAN78XX_DEFAULT_BURST_CAP_SIZE div LAN78XX_SS_USB_PKT_SIZE;
      
      {Set RX and TX Sizes}
      PLAN78XXNetwork(Network).ReceiveRequestSize:=LAN78XX_DEFAULT_BURST_CAP_SIZE;
      PLAN78XXNetwork(Network).TransmitRequestSize:=ETHERNET_MAX_PACKET_SIZE + LAN78XX_TX_OVERHEAD;
      PLAN78XXNetwork(Network).ReceiveEntryCount:=4;
      PLAN78XXNetwork(Network).TransmitEntryCount:=4;
      PLAN78XXNetwork(Network).ReceivePacketCount:=LAN78XX_DEFAULT_BURST_CAP_SIZE div (ETHERNET_MIN_PACKET_SIZE + LAN78XX_RX_OVERHEAD);
      PLAN78XXNetwork(Network).TransmitPacketCount:=1;
     end
    else if Device.Speed = USB_SPEED_HIGH then 
     begin
      Buffer:=LAN78XX_DEFAULT_BURST_CAP_SIZE div LAN78XX_HS_USB_PKT_SIZE;
      
      {Set RX and TX Sizes}
      PLAN78XXNetwork(Network).ReceiveRequestSize:=LAN78XX_DEFAULT_BURST_CAP_SIZE;
      PLAN78XXNetwork(Network).TransmitRequestSize:=ETHERNET_MAX_PACKET_SIZE + LAN78XX_TX_OVERHEAD;      
      PLAN78XXNetwork(Network).ReceiveEntryCount:=LAN78XX_RX_MAX_QUEUE_MEMORY div PLAN78XXNetwork(Network).ReceiveRequestSize;
      PLAN78XXNetwork(Network).TransmitEntryCount:=LAN78XX_TX_MAX_QUEUE_MEMORY div PLAN78XXNetwork(Network).TransmitRequestSize;
      PLAN78XXNetwork(Network).ReceivePacketCount:=LAN78XX_DEFAULT_BURST_CAP_SIZE div (ETHERNET_MIN_PACKET_SIZE + LAN78XX_RX_OVERHEAD);
      PLAN78XXNetwork(Network).TransmitPacketCount:=1;
     end
    else
     begin
      Buffer:=LAN78XX_DEFAULT_BURST_CAP_SIZE div LAN78XX_FS_USB_PKT_SIZE;
      
      {Set RX and TX Sizes}
      PLAN78XXNetwork(Network).ReceiveRequestSize:=LAN78XX_DEFAULT_BURST_CAP_SIZE;
      PLAN78XXNetwork(Network).TransmitRequestSize:=ETHERNET_MAX_PACKET_SIZE + LAN78XX_TX_OVERHEAD;      
      PLAN78XXNetwork(Network).ReceiveEntryCount:=4;
      PLAN78XXNetwork(Network).TransmitEntryCount:=4;
      PLAN78XXNetwork(Network).ReceivePacketCount:=LAN78XX_DEFAULT_BURST_CAP_SIZE div (ETHERNET_MIN_PACKET_SIZE + LAN78XX_RX_OVERHEAD);
      PLAN78XXNetwork(Network).TransmitPacketCount:=1;
    end;
    {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
    if USB_LOG_ENABLED then
     begin
      USBLogDebug(Device,'LAN78XX: BurstCap=' + IntToStr(Buffer));
      USBLogDebug(Device,'LAN78XX: ReceiveRequestSize=' + IntToStr(PLAN78XXNetwork(Network).ReceiveRequestSize));
      USBLogDebug(Device,'LAN78XX: TransmitRequestSize=' + IntToStr(PLAN78XXNetwork(Network).TransmitRequestSize));
      USBLogDebug(Device,'LAN78XX: ReceiveEntryCount=' + IntToStr(PLAN78XXNetwork(Network).ReceiveEntryCount));
      USBLogDebug(Device,'LAN78XX: TransmitEntryCount=' + IntToStr(PLAN78XXNetwork(Network).TransmitEntryCount));
      USBLogDebug(Device,'LAN78XX: ReceivePacketCount=' + IntToStr(PLAN78XXNetwork(Network).ReceivePacketCount));
      USBLogDebug(Device,'LAN78XX: TransmitPacketCount=' + IntToStr(PLAN78XXNetwork(Network).TransmitPacketCount));
     end; 
    {$ENDIF}
    
    {Set the maximum USB (not network) packets per USB Receive transfer}
    Status:=LAN78XXWriteRegister(Device,LAN78XX_BURST_CAP,Buffer);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set burst cap size: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Set the USB Bulk IN delay (How long to delay a bulk IN request once a packet has been received)}
    Status:=LAN78XXWriteRegister(Device,LAN78XX_BULK_IN_DLY,LAN78XX_DEFAULT_BULK_IN_DELAY);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set bulk IN delay: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Get Hardware Configuration}
    Status:=LAN78XXReadRegister(Device,LAN78XX_HW_CFG,Buffer);
    if Status <> USB_STATUS_SUCCESS then Exit;
    
    {Enable multiple ethernet frames per packet}
    Buffer:=Buffer or LAN78XX_HW_CFG_MEF;
    
    {If no valid EEPROM and no valid OTP, enable the LEDs by default}
    if (LAN78XXReadEEPROM(Device,0,0,nil) <> USB_STATUS_SUCCESS) and (LAN78XXReadOTP(Device,0,0,nil) <> USB_STATUS_SUCCESS) then
     begin
      Buffer:=Buffer or LAN78XX_HW_CFG_LED0_EN or LAN78XX_HW_CFG_LED1_EN;
     end;  
    
    {Set Hardware Configuration}
    Status:=LAN78XXWriteRegister(Device,LAN78XX_HW_CFG,Buffer);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set hardware configuration: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Enable USB burst cap mode} 
    Status:=LAN78XXSetRegisterBits(Device,LAN78XX_USB_CFG0,LAN78XX_USB_CFG_BCE);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to enable burst cap mode: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Set RX FIFO size}
    Buffer:=(LAN78XX_MAX_RX_FIFO_SIZE - 512) div 512;
    Status:=LAN78XXWriteRegister(Device,LAN78XX_FCT_RX_FIFO_END,Buffer);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set RX FIFO size: ' + USBStatusToString(Status));
      Exit;
     end; 
   
    {Set TX FIFO size}
    Buffer:=(LAN78XX_MAX_TX_FIFO_SIZE - 512) div 512;
    Status:=LAN78XXWriteRegister(Device,LAN78XX_FCT_TX_FIFO_END,Buffer);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set TX FIFO size: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Clear INT_STS, FLOW and FCT_FLOW registers}
    Status:=LAN78XXWriteRegister(Device,LAN78XX_INT_STS,LAN78XX_INT_STS_CLEAR_ALL);
    if Status <> USB_STATUS_SUCCESS then Exit;
    Status:=LAN78XXWriteRegister(Device,LAN78XX_FLOW,0);
    if Status <> USB_STATUS_SUCCESS then Exit;
    Status:=LAN78XXWriteRegister(Device,LAN78XX_FCT_FLOW,0);
    if Status <> USB_STATUS_SUCCESS then Exit;
    
    {Enable broadcast packets and destination address filtering}
    Status:=LAN78XXSetRegisterBits(Device,LAN78XX_RFE_CTL,LAN78XX_RFE_CTL_BCAST_EN or LAN78XX_RFE_CTL_DA_PERFECT);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to enable receive filtering: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    //To Do //lan78xx_set_multicast
    
    {Reset PHY}
    Status:=LAN78XXSetRegisterBits(Device,LAN78XX_PMT_CTL,LAN78XX_PMT_CTL_PHY_RST);
    if Status <> USB_STATUS_SUCCESS then Exit;
    Current:=GetTickCount64;
    repeat
     MillisecondDelay(1);
     Status:=LAN78XXReadRegister(Device,LAN78XX_PMT_CTL,Buffer);
     if (Status <> USB_STATUS_SUCCESS) or (GetTickCount64 > (Current + MILLISECONDS_PER_SECOND)) then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to reset PHY: ' + USBStatusToString(Status));
       Exit;
      end;
    until ((Buffer and LAN78XX_PMT_CTL_PHY_RST) = 0) and ((Buffer and LAN78XX_PMT_CTL_READY) <> 0);
    
    {Enable Auto Negotiation}
    Status:=LAN78XXSetRegisterBits(Device,LAN78XX_MAC_CR,LAN78XX_MAC_CR_AUTO_DUPLEX or LAN78XX_MAC_CR_AUTO_SPEED);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set MAC control: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    try
     {Allocate Receive Queue Buffer}
     Network.ReceiveQueue.Buffer:=BufferCreate(SizeOf(TNetworkEntry),PLAN78XXNetwork(Network).ReceiveEntryCount);
     if Network.ReceiveQueue.Buffer = INVALID_HANDLE_VALUE then
      begin
       if NETWORK_LOG_ENABLED then NetworkLogError(Network,'LAN78XX: Failed to create receive queue buffer');
       
       Exit;
      end;
     
     {Allocate Receive Queue Semaphore}
     Network.ReceiveQueue.Wait:=SemaphoreCreate(0);
     if Network.ReceiveQueue.Wait = INVALID_HANDLE_VALUE then
      begin
       if NETWORK_LOG_ENABLED then NetworkLogError(Network,'LAN78XX: Failed to create receive queue semaphore');
       
       Exit;
      end;
     
     {Allocate Receive Queue Buffers}
     Entry:=BufferIterate(Network.ReceiveQueue.Buffer,nil);
     while Entry <> nil do
      begin
       {Initialize Entry}
       Entry.Size:=PLAN78XXNetwork(Network).ReceiveRequestSize;
       Entry.Offset:=LAN78XX_RX_OVERHEAD;
       Entry.Count:=0; {PLAN78XXNetwork(Network).ReceivePacketCount}
       
       {Allocate USB Request Buffer}
       Entry.Buffer:=USBBufferAllocate(Device,Entry.Size);
       if Entry.Buffer = nil then
        begin
         if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to allocate USB receive buffer');
         
         Exit;
        end;
       
       {Initialize Packets}
       SetLength(Entry.Packets,PLAN78XXNetwork(Network).ReceivePacketCount);
       
       {Initialize First Packet}
       Entry.Packets[0].Buffer:=Entry.Buffer;
       Entry.Packets[0].Data:=Entry.Buffer + Entry.Offset;
       Entry.Packets[0].Length:=Entry.Size - Entry.Offset;
       
       Entry:=BufferIterate(Network.ReceiveQueue.Buffer,Entry);
      end;
     
     {Allocate Receive Queue Entries}
     SetLength(Network.ReceiveQueue.Entries,PLAN78XXNetwork(Network).ReceiveEntryCount);
     
     {Allocate Transmit Queue Buffer}
     Network.TransmitQueue.Buffer:=BufferCreate(SizeOf(TNetworkEntry),PLAN78XXNetwork(Network).TransmitEntryCount);
     if Network.TransmitQueue.Buffer = INVALID_HANDLE_VALUE then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to create transmit queue buffer');
       
       Exit;
      end;
     
     {Allocate Transmit Queue Semaphore}
     Network.TransmitQueue.Wait:=SemaphoreCreate(PLAN78XXNetwork(Network).TransmitEntryCount);
     if Network.TransmitQueue.Wait = INVALID_HANDLE_VALUE then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to create transmit queue semaphore');
       
       Exit;
      end;

     {Allocate Transmit Queue Buffers}
     Entry:=BufferIterate(Network.TransmitQueue.Buffer,nil);
     while Entry <> nil do
      begin
       {Initialize Entry}
       Entry.Size:=PLAN78XXNetwork(Network).TransmitRequestSize;
       Entry.Offset:=LAN78XX_TX_OVERHEAD;
       Entry.Count:=PLAN78XXNetwork(Network).TransmitPacketCount;
       
       {Allocate USB Request Buffer}
       Entry.Buffer:=USBBufferAllocate(Device,Entry.Size);
       if Entry.Buffer = nil then
        begin
         if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to allocate USB transmit buffer');
         
         Exit;
        end;
        
       {Initialize Packets}
       SetLength(Entry.Packets,PLAN78XXNetwork(Network).TransmitPacketCount);
      
       {Initialize First Packet}
       Entry.Packets[0].Buffer:=Entry.Buffer;
       Entry.Packets[0].Data:=Entry.Buffer + Entry.Offset;
       Entry.Packets[0].Length:=Entry.Size - Entry.Offset;
       
       Entry:=BufferIterate(Network.TransmitQueue.Buffer,Entry);
      end;
      
     {Allocate Transmit Queue Entries}
     SetLength(Network.TransmitQueue.Entries,PLAN78XXNetwork(Network).TransmitEntryCount);
     
     {Allocate Transmit Request}
     PLAN78XXNetwork(Network).TransmitRequest:=USBRequestAllocate(Device,PLAN78XXNetwork(Network).TransmitEndpoint,LAN78XXTransmitComplete,0,nil);
     if PLAN78XXNetwork(Network).TransmitRequest = nil then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to allocate transmit request');
       
       Exit;
      end;

     {Allocate Receive Request}
     PLAN78XXNetwork(Network).ReceiveRequest:=USBRequestAllocate(Device,PLAN78XXNetwork(Network).ReceiveEndpoint,LAN78XXReceiveComplete,0,nil);
     if PLAN78XXNetwork(Network).ReceiveRequest = nil then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to allocate receive request');
       
       Exit;
      end;
      
     {Submit Receive Request}
     {Get Entry}
     Entry:=BufferGet(Network.ReceiveQueue.Buffer);
     if Entry <> nil then
      begin
       {Update Pending}
       Inc(PLAN78XXNetwork(Network).PendingCount);
       
       {Update Entry}
       Entry.DriverData:=Network;
       
       {Initialize Request}
       USBRequestInitialize(PLAN78XXNetwork(Network).ReceiveRequest,LAN78XXReceiveComplete,Entry.Buffer,Entry.Size,Entry);
       
       {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
       if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Submitting receive request');
       {$ENDIF}
       
       {Submit Request}
       Status:=USBRequestSubmit(PLAN78XXNetwork(Network).ReceiveRequest);
       if Status <> USB_STATUS_SUCCESS then
        begin
         if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to submit receive request: ' + USBStatusToString(Status));
         
         {Update Pending}
         Dec(PLAN78XXNetwork(Network).PendingCount);
         
         {Update Entry}
         Entry.DriverData:=nil;
         
         {Free Entry}
         BufferFree(Entry);
         Exit;
        end; 
      end
     else
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to get receive buffer entry');
       
       Exit;
      end;      
     
     {Allocate Interrupt Request}
     PLAN78XXNetwork(Network).InterruptRequest:=USBRequestAllocate(Device,PLAN78XXNetwork(Network).InterruptEndpoint,LAN78XXInterruptComplete,PLAN78XXNetwork(Network).InterruptEndpoint.wMaxPacketSize,Network); 
     if PLAN78XXNetwork(Network).InterruptRequest = nil then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to allocate interrupt request');
       
       Exit;
      end;
      
     {Update Pending}
     Inc(PLAN78XXNetwork(Network).PendingCount);
      
     {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
     if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Submitting interrupt request');
     {$ENDIF}
     
     {Submit Interrupt Request}
     Status:=USBRequestSubmit(PLAN78XXNetwork(Network).InterruptRequest);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to submit interrupt request: ' + USBStatusToString(Status));
       
       {Update Pending}
       Dec(PLAN78XXNetwork(Network).PendingCount);
       Exit;
      end;
     
     {Enable Endpoint Interrupts}
     Status:=LAN78XXSetRegisterBits(Device,LAN78XX_INT_EP_CTL,LAN78XX_INT_EP_PHY_INT_EN);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to enable endpoint interrupts: ' + USBStatusToString(Status));
       Exit;
      end; 
     
     {Enable TX}
     Status:=LAN78XXSetRegisterBits(Device,LAN78XX_MAC_TX,LAN78XX_MAC_TX_TXEN);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to enable transmit: ' + USBStatusToString(Status));
       Exit;
      end; 
 
     Status:=LAN78XXSetRegisterBits(Device,LAN78XX_FCT_TX_CTL,LAN78XX_FCT_TX_CTL_EN);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set transmit control: ' + USBStatusToString(Status));
       Exit;
      end; 
     
     {Set Max RX Frame Size}
     Status:=LAN78XXModifyRegister(Device,LAN78XX_MAC_RX,not(LAN78XX_MAC_RX_MAX_SIZE_MASK),ETHERNET_MAX_PACKET_SIZE shl LAN78XX_MAC_RX_MAX_SIZE_SHIFT);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set maximum receive size: ' + USBStatusToString(Status));
       Exit;
      end; 
     
     {Enable RX}
     Status:=LAN78XXSetRegisterBits(Device,LAN78XX_MAC_RX,LAN78XX_MAC_RX_RXEN);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to enable receive: ' + USBStatusToString(Status));
       Exit;
      end; 
     
     Status:=LAN78XXSetRegisterBits(Device,LAN78XX_FCT_RX_CTL,LAN78XX_FCT_RX_CTL_EN);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set receive control: ' + USBStatusToString(Status));
       Exit;
      end; 
     
     {Initialize PHY (Interrupts, AutoMDIX, LEDs)}
     Status:=LAN78XXPHYInitialize(Device);
     if Status <> USB_STATUS_SUCCESS then
      begin
       if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to initialize PHY: ' + USBStatusToString(Status));
       {Exit;} {Do not fail}
      end; 
     
     {Set State to Open}
     Network.NetworkState:=NETWORK_STATE_OPEN;
 
     {Notify the State}
     NotifierNotify(@Network.Device,DEVICE_NOTIFICATION_OPEN); 
  
     {Get Network Status}
     LAN78XXPHYRead(Device,MII_BMSR,Value);
     if (Value and BMSR_LSTATUS) <> 0 then
      begin
       {Set Status to Up}
       Network.NetworkStatus:=NETWORK_STATUS_UP;
       
       {Notify the Status}
       NotifierNotify(@Network.Device,DEVICE_NOTIFICATION_UP); 
      end;
  
     {Return Result}
     Result:=ERROR_SUCCESS;
    finally
     {Check Result}
     if Result <> ERROR_SUCCESS then
      begin
       {Check Interrupt Request}
       if PLAN78XXNetwork(Network).InterruptRequest <> nil then
        begin
         {Cancel Interrupt Request}
         USBRequestCancel(PLAN78XXNetwork(Network).InterruptRequest);
         
         {Release Interrupt Request}
         USBRequestRelease(PLAN78XXNetwork(Network).InterruptRequest);
        end;
       
       {Check Receive Request}
       if PLAN78XXNetwork(Network).ReceiveRequest <> nil then
        begin
         {Cancel Receive Request}
         USBRequestCancel(PLAN78XXNetwork(Network).ReceiveRequest);
         
         {Release Receive Request}
         USBRequestRelease(PLAN78XXNetwork(Network).ReceiveRequest);
        end;
       
       {Check Transmit Request}
       if PLAN78XXNetwork(Network).TransmitRequest <> nil then
        begin
         {Release Transmit Request}
         USBRequestRelease(PLAN78XXNetwork(Network).TransmitRequest);
        end;
       
       {Check Transmit Queue Buffer}
       if Network.TransmitQueue.Buffer <> INVALID_HANDLE_VALUE then
        begin
         {Deallocate Transmit Queue Entries}
         SetLength(Network.TransmitQueue.Entries,0);
          
         {Deallocate Transmit Queue Buffers}
         Entry:=BufferIterate(Network.TransmitQueue.Buffer,nil);
         while Entry <> nil do
          begin
           {Release USB Request Buffer}
           USBBufferRelease(Entry.Buffer);
            
           {Deinitialize Packets}
           SetLength(Entry.Packets,0);
           
           Entry:=BufferIterate(Network.TransmitQueue.Buffer,Entry);
          end;
        
         {Deallocate Transmit Queue Buffer}
         BufferDestroy(Network.TransmitQueue.Buffer);
         
         Network.TransmitQueue.Buffer:=INVALID_HANDLE_VALUE;
        end;
       
       {Check Transmit Queue Semaphore}
       if Network.TransmitQueue.Wait <> INVALID_HANDLE_VALUE then
        begin
         {Deallocate Transmit Queue Semaphore}
         SemaphoreDestroy(Network.TransmitQueue.Wait);
          
         Network.TransmitQueue.Wait:=INVALID_HANDLE_VALUE;
        end;
       
       {Check Receive Queue Buffer}
       if Network.ReceiveQueue.Buffer <> INVALID_HANDLE_VALUE then
        begin
         {Deallocate Receive Queue Entries}
         SetLength(Network.ReceiveQueue.Entries,0);
        
         {Deallocate Receive Queue Buffers}
         Entry:=BufferIterate(Network.ReceiveQueue.Buffer,nil);
         while Entry <> nil do
          begin
           {Release USB Request Buffer}
           USBBufferRelease(Entry.Buffer);
          
           {Initialize Packets}
           SetLength(Entry.Packets,0);
           
           Entry:=BufferIterate(Network.ReceiveQueue.Buffer,Entry);
          end;
         
         {Deallocate Receive Queue Buffer}
         BufferDestroy(Network.ReceiveQueue.Buffer);
         
         Network.ReceiveQueue.Buffer:=INVALID_HANDLE_VALUE;
        end;
        
       {Check Receive Queue Semaphore}
       if Network.ReceiveQueue.Wait <> INVALID_HANDLE_VALUE then
        begin
         {Deallocate Receive Queue Semaphore}
         SemaphoreDestroy(Network.ReceiveQueue.Wait);
          
         Network.ReceiveQueue.Wait:=INVALID_HANDLE_VALUE;
        end;
      end;
    end;    
   finally
    {Release the Lock}
    MutexUnlock(Network.Lock);
   end;
  end
 else
  begin
   Result:=ERROR_CAN_NOT_COMPLETE;
  end;  
end;
 
{==============================================================================}

function LAN78XXNetworkClose(Network:PNetworkDevice):LongWord;
{Implementation of NetworkDeviceClose for the LAN78XX device}
{Note: Not intended to be called directly by applications, use NetworkDeviceClose instead}
var
 Status:LongWord;
 Message:TMessage;
 Device:PUSBDevice;
 Entry:PNetworkEntry;
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;

 {Check Network}
 if Network = nil then Exit;
 if Network.Device.Signature <> DEVICE_SIGNATURE then Exit;

 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX: Network Close');
 {$ENDIF}
 
 {Get Device}
 Device:=PUSBDevice(Network.Device.DeviceData);
 if Device = nil then Exit;
 
 {Check State}
 Result:=ERROR_NOT_OPEN;
 if Network.NetworkState <> NETWORK_STATE_OPEN then Exit;
 
 {Set State to Closing}
 Result:=ERROR_OPERATION_FAILED;
 if NetworkDeviceSetState(Network,NETWORK_STATE_CLOSING) <> ERROR_SUCCESS then Exit;
 
 {Acquire the Lock}
 if MutexLock(Network.Lock) = ERROR_SUCCESS then
  begin
   try
    {Check Pending}
    if PLAN78XXNetwork(Network).PendingCount <> 0 then
     begin
      {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
      if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Waiting for ' + IntToStr(PLAN78XXNetwork(Network).PendingCount) + ' pending requests to complete');
      {$ENDIF}

      {Wait for Pending}
 
      {Setup Waiter}
      PLAN78XXNetwork(Network).WaiterThread:=GetCurrentThreadId; 
   
      {Release the Lock}
      MutexUnlock(Network.Lock);
   
      {Wait for Message}
      ThreadReceiveMessage(Message); 
      
      {Acquire the Lock}
      if MutexLock(Network.Lock) <> ERROR_SUCCESS then Exit;
     end;
       
    {Set State to Closed}
    Network.NetworkState:=NETWORK_STATE_CLOSED;
 
    {Notify the State}
    NotifierNotify(@Network.Device,DEVICE_NOTIFICATION_CLOSE); 

    {Disable TX}
    Status:=LAN78XXClearRegisterBits(Device,LAN78XX_MAC_TX,LAN78XX_MAC_TX_TXEN);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to disable transmit: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Disable RX}
    Status:=LAN78XXClearRegisterBits(Device,LAN78XX_MAC_RX,LAN78XX_MAC_RX_RXEN);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to disable receive: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Disable LEDs}
    Status:=LAN78XXClearRegisterBits(Device,LAN78XX_HW_CFG,LAN78XX_HW_CFG_LED0_EN or LAN78XX_HW_CFG_LED1_EN);
    if Status <> USB_STATUS_SUCCESS then
     begin
      if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set hardware configuration: ' + USBStatusToString(Status));
      Exit;
     end; 
    
    {Check Interrupt Request}
    if PLAN78XXNetwork(Network).InterruptRequest <> nil then
     begin
      {Release Interrupt Request}
      USBRequestRelease(PLAN78XXNetwork(Network).InterruptRequest);
     end;
    
    {Check Receive Request}
    if PLAN78XXNetwork(Network).ReceiveRequest <> nil then
     begin
      {Release Receive Request}
      USBRequestRelease(PLAN78XXNetwork(Network).ReceiveRequest);
     end;
    
    {Check Transmit Request}
    if PLAN78XXNetwork(Network).TransmitRequest <> nil then
     begin
      {Release Transmit Request}
      USBRequestRelease(PLAN78XXNetwork(Network).TransmitRequest);
     end;
    
    {Check Transmit Queue Buffer}
    if Network.TransmitQueue.Buffer <> INVALID_HANDLE_VALUE then
     begin
      {Deallocate Transmit Queue Entries}
      SetLength(Network.TransmitQueue.Entries,0);
       
      {Deallocate Transmit Queue Buffers}
      Entry:=BufferIterate(Network.TransmitQueue.Buffer,nil);
      while Entry <> nil do
       begin
        {Release USB Request Buffer}
        USBBufferRelease(Entry.Buffer);
        
        {Deinitialize Packets}
        SetLength(Entry.Packets,0);
        
        Entry:=BufferIterate(Network.TransmitQueue.Buffer,Entry);
       end;
     
      {Deallocate Transmit Queue Buffer}
      BufferDestroy(Network.TransmitQueue.Buffer);
      
      Network.TransmitQueue.Buffer:=INVALID_HANDLE_VALUE;
     end;
    
    {Check Transmit Queue Semaphore}
    if Network.TransmitQueue.Wait <> INVALID_HANDLE_VALUE then
     begin
      {Deallocate Transmit Queue Semaphore}
      SemaphoreDestroy(Network.TransmitQueue.Wait);
       
      Network.TransmitQueue.Wait:=INVALID_HANDLE_VALUE;
     end;
    
    {Check Receive Queue Buffer}
    if Network.ReceiveQueue.Buffer <> INVALID_HANDLE_VALUE then
     begin
      {Deallocate Receive Queue Entries}
      SetLength(Network.ReceiveQueue.Entries,0);
     
      {Deallocate Receive Queue Buffers}
      Entry:=BufferIterate(Network.ReceiveQueue.Buffer,nil);
      while Entry <> nil do
       begin
        {Release USB Request Buffer}
        USBBufferRelease(Entry.Buffer);
       
        {Initialize Packets}
        SetLength(Entry.Packets,0);
        
        Entry:=BufferIterate(Network.ReceiveQueue.Buffer,Entry);
       end;
      
      {Deallocate Receive Queue Buffer}
      BufferDestroy(Network.ReceiveQueue.Buffer);
      
      Network.ReceiveQueue.Buffer:=INVALID_HANDLE_VALUE;
     end;
     
    {Check Receive Queue Semaphore}
    if Network.ReceiveQueue.Wait <> INVALID_HANDLE_VALUE then
     begin
      {Deallocate Receive Queue Semaphore}
      SemaphoreDestroy(Network.ReceiveQueue.Wait);
       
      Network.ReceiveQueue.Wait:=INVALID_HANDLE_VALUE;
     end;
    
    {Return Result}
    Result:=ERROR_SUCCESS;
   finally
    {Release the Lock}
    MutexUnlock(Network.Lock);
   end;
  end
 else
  begin
   Result:=ERROR_CAN_NOT_COMPLETE;
  end;  
end;

{==============================================================================}

function LAN78XXNetworkControl(Network:PNetworkDevice;Request:Integer;Argument1:PtrUInt;var Argument2:PtrUInt):LongWord;
{Implementation of NetworkDeviceControl for the LAN78XX device}
{Note: Not intended to be called directly by applications, use NetworkDeviceControl instead}
var
 Value:Word;
 Status:LongWord;
 Device:PUSBDevice;
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;

 {Check Network}
 if Network = nil then Exit;
 if Network.Device.Signature <> DEVICE_SIGNATURE then Exit;
 
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX: Network Control');
 {$ENDIF}
 
 {Get Device}
 Device:=PUSBDevice(Network.Device.DeviceData);
 if Device = nil then Exit;
 
 {Acquire the Lock}
 if MutexLock(Network.Lock) = ERROR_SUCCESS then
  begin
   try
    {Set Result}
    Result:=ERROR_OPERATION_FAILED;
    Status:=USB_STATUS_SUCCESS;
    
    {Check Request}
    case Request of
     NETWORK_CONTROL_CLEAR_STATS:begin
       {Clear Statistics}
       {Network}
       Network.ReceiveBytes:=0;
       Network.ReceiveCount:=0;
       Network.ReceiveErrors:=0;
       Network.TransmitBytes:=0;
       Network.TransmitCount:=0;
       Network.TransmitErrors:=0;
       Network.StatusCount:=0;
       Network.StatusErrors:=0;
       Network.BufferOverruns:=0;
       Network.BufferUnavailable:=0;
      end; 
     NETWORK_CONTROL_GET_STATS:begin
       {Get Statistics}
       if Argument2 < SizeOf(TNetworkStatistics) then Exit;
       
       {Network}
       PNetworkStatistics(Argument1).ReceiveBytes:=Network.ReceiveBytes;
       PNetworkStatistics(Argument1).ReceiveCount:=Network.ReceiveCount;
       PNetworkStatistics(Argument1).ReceiveErrors:=Network.ReceiveErrors;
       PNetworkStatistics(Argument1).TransmitBytes:=Network.TransmitBytes;
       PNetworkStatistics(Argument1).TransmitCount:=Network.TransmitCount;
       PNetworkStatistics(Argument1).TransmitErrors:=Network.TransmitErrors;
       PNetworkStatistics(Argument1).StatusCount:=Network.StatusCount;
       PNetworkStatistics(Argument1).StatusErrors:=Network.StatusErrors;
       PNetworkStatistics(Argument1).BufferOverruns:=Network.BufferOverruns;
       PNetworkStatistics(Argument1).BufferUnavailable:=Network.BufferUnavailable;
      end;     
     NETWORK_CONTROL_SET_MAC:begin     
       {Set the MAC for this device}
       Status:=LAN78XXSetMacAddress(Device,PHardwareAddress(Argument1));
      end; 
     NETWORK_CONTROL_GET_MAC:begin    
       {Get the MAC for this device}
       Status:=LAN78XXGetMacAddress(Device,PHardwareAddress(Argument1));
      end; 
     NETWORK_CONTROL_SET_LOOPBACK:begin  
       {Set Loopback Mode}          
       if LongBool(Argument1) then
        begin
         Status:=LAN78XXSetRegisterBits(Device,LAN78XX_MAC_CR,LAN78XX_MAC_CR_LOOPBACK);
        end
       else
        begin
         Status:=LAN78XXClearRegisterBits(Device,LAN78XX_MAC_CR,LAN78XX_MAC_CR_LOOPBACK);
        end;     
      end; 
     NETWORK_CONTROL_RESET:begin       
       {Reset the device}  
       //To Do
      end; 
     NETWORK_CONTROL_DISABLE:begin     
       {Disable the device}
       //To Do
      end; 
     NETWORK_CONTROL_GET_HARDWARE:begin     
       {Get Hardware address for this device}
       Status:=LAN78XXGetMacAddress(Device,PHardwareAddress(Argument1));
      end; 
     NETWORK_CONTROL_GET_BROADCAST:begin     
       {Get Broadcast address for this device}
       PHardwareAddress(Argument1)^:=ETHERNET_BROADCAST;
      end; 
     NETWORK_CONTROL_GET_MTU:begin     
       {Get MTU for this device}
       Argument2:=ETHERNET_MTU; 
      end; 
     NETWORK_CONTROL_GET_HEADERLEN:begin
       {Get Header length for this device}
       Argument2:=ETHERNET_HEADER_SIZE;
      end;  
     NETWORK_CONTROL_GET_LINK:begin
       {Get Link State for this device}
       LAN78XXPHYRead(Device,MII_BMSR,Value);
       if (Value and BMSR_LSTATUS) <> 0 then
        begin
         {Link Up}
         Argument2:=NETWORK_LINK_UP;
        end
       else
        begin
         {Link Down}
         Argument2:=NETWORK_LINK_DOWN;
        end;
      end; 
     else
      begin
       Exit;
      end;   
    end;
    
    {Check Status}
    if Status <> USB_STATUS_SUCCESS then Exit;

    {Return Result}  
    Result:=ERROR_SUCCESS;
   finally
    {Release the Lock}
    MutexUnlock(Network.Lock);
   end;
  end
 else
  begin
   Result:=ERROR_CAN_NOT_COMPLETE;
  end;  
end;

{==============================================================================}

function LAN78XXBufferAllocate(Network:PNetworkDevice;var Entry:PNetworkEntry):LongWord;
{Implementation of NetworkBufferAllocate for the LAN78XX device}
{Note: Not intended to be called directly by applications, use NetworkBufferAllocate instead}
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;
 
 {Setup Entry}
 Entry:=nil;
 
 {Check Network}
 if Network = nil then Exit;
 if Network.Device.Signature <> DEVICE_SIGNATURE then Exit;
 
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX: Buffer Allocate');
 {$ENDIF}
 
 {Check State}
 Result:=ERROR_NOT_READY;
 if Network.NetworkState <> NETWORK_STATE_OPEN then Exit;
 
 {Set Result}
 Result:=ERROR_OPERATION_FAILED;

 {Wait for Entry (Transmit Buffer)}
 Entry:=BufferGet(Network.TransmitQueue.Buffer);
 if Entry <> nil then
  begin
   {Update Entry}
   Entry.Size:=PLAN78XXNetwork(Network).TransmitRequestSize;
   Entry.Offset:=LAN78XX_TX_OVERHEAD;
   Entry.Count:=PLAN78XXNetwork(Network).TransmitPacketCount;
   
   {Update First Packet}
   Entry.Packets[0].Buffer:=Entry.Buffer;
   Entry.Packets[0].Data:=Entry.Buffer + Entry.Offset;
   Entry.Packets[0].Length:=Entry.Size - Entry.Offset;
   
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
   if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX:  Entry.Size = ' + IntToStr(Entry.Size));
   if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX:  Entry.Offset = ' + IntToStr(Entry.Offset));
   if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX:  Entry.Count = ' + IntToStr(Entry.Count));
   if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX:  Entry.Packets[0].Length = ' + IntToStr(Entry.Packets[0].Length));
   {$ENDIF}
   
   {Return Result}
   Result:=ERROR_SUCCESS;
  end;
end;

{==============================================================================}

function LAN78XXBufferRelease(Network:PNetworkDevice;Entry:PNetworkEntry):LongWord;
{Implementation of NetworkBufferRelease for the LAN78XX device}
{Note: Not intended to be called directly by applications, use NetworkBufferRelease instead}
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;
 
 {Check Network}
 if Network = nil then Exit;
 if Network.Device.Signature <> DEVICE_SIGNATURE then Exit;
 
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX: Buffer Release');
 {$ENDIF}
 
 {Check Entry}
 if Entry = nil then Exit;
 
 {Check State}
 Result:=ERROR_NOT_READY;
 if Network.NetworkState <> NETWORK_STATE_OPEN then Exit;
 
 {Acquire the Lock}
 if MutexLock(Network.Lock) = ERROR_SUCCESS then
  begin
   try
    {Free Entry (Receive Buffer)}
    Result:=BufferFree(Entry);
   finally
    {Release the Lock}
    MutexUnlock(Network.Lock);
   end;
  end
 else
  begin
   Result:=ERROR_CAN_NOT_COMPLETE;
  end;  
end;

{==============================================================================}

function LAN78XXBufferReceive(Network:PNetworkDevice;var Entry:PNetworkEntry):LongWord;
{Implementation of NetworkBufferReceive for the LAN78XX device}
{Note: Not intended to be called directly by applications, use NetworkBufferReceive instead}
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;
 
 {Setup Entry}
 Entry:=nil;
 
 {Check Network}
 if Network = nil then Exit;
 if Network.Device.Signature <> DEVICE_SIGNATURE then Exit;
 
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX: Buffer Receive');
 {$ENDIF}
  
 {Check State}
 Result:=ERROR_NOT_READY;
 if Network.NetworkState <> NETWORK_STATE_OPEN then Exit;
 
 {Wait for Entry}
 if SemaphoreWait(Network.ReceiveQueue.Wait) = ERROR_SUCCESS then
  begin
   {Acquire the Lock}
   if MutexLock(Network.Lock) = ERROR_SUCCESS then
    begin
     try
      {Remove Entry}
      Entry:=Network.ReceiveQueue.Entries[Network.ReceiveQueue.Start];
      
      {Update Start}
      Network.ReceiveQueue.Start:=(Network.ReceiveQueue.Start + 1) mod PLAN78XXNetwork(Network).ReceiveEntryCount;
      
      {Update Count}
      Dec(Network.ReceiveQueue.Count);
      
      {Return Result}
      Result:=ERROR_SUCCESS;
     finally
      {Release the Lock}
      MutexUnlock(Network.Lock);
     end;
    end
   else
    begin
     Result:=ERROR_CAN_NOT_COMPLETE;
    end;  
  end
 else
  begin
   Result:=ERROR_CAN_NOT_COMPLETE;
  end;  
end;

{==============================================================================}

function LAN78XXBufferTransmit(Network:PNetworkDevice;Entry:PNetworkEntry):LongWord;
{Implementation of NetworkBufferTransmit for the LAN78XX device}
{Note: Not intended to be called directly by applications, use NetworkBufferTransmit instead}
var
 Empty:Boolean;
begin
 {}
 Result:=ERROR_INVALID_PARAMETER;
 
 {Check Network}
 if Network = nil then Exit;
 if Network.Device.Signature <> DEVICE_SIGNATURE then Exit;
 
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(Network,'LAN78XX: Buffer Transmit');
 {$ENDIF}
  
 {Check Entry}
 if Entry = nil then Exit;
 if (Entry.Count = 0) or (Entry.Count > 1) then Exit;
 
 {Check State}
 Result:=ERROR_NOT_READY;
 if Network.NetworkState <> NETWORK_STATE_OPEN then Exit;
 
 {Wait for Entry}
 if SemaphoreWait(Network.TransmitQueue.Wait) = ERROR_SUCCESS then
  begin
   {Acquire the Lock}
   if MutexLock(Network.Lock) = ERROR_SUCCESS then
    begin
     try
      {Check Empty}
      Empty:=(Network.TransmitQueue.Count = 0);
      
      {Add Entry}
      Network.TransmitQueue.Entries[(Network.TransmitQueue.Start + Network.TransmitQueue.Count) mod PLAN78XXNetwork(Network).TransmitEntryCount]:=Entry;
    
      {Update Count}
      Inc(Network.TransmitQueue.Count);
    
      {Check Empty}
      if Empty then
       begin
        {Start Transmit}
        LAN78XXTransmitStart(PLAN78XXNetwork(Network));
       end; 
      
      {Return Result}
      Result:=ERROR_SUCCESS;
     finally
      {Release the Lock}
      MutexUnlock(Network.Lock);
     end;
    end
   else
    begin
     Result:=ERROR_CAN_NOT_COMPLETE;
    end;  
  end
 else
  begin
   Result:=ERROR_CAN_NOT_COMPLETE;
  end;  
end;

{==============================================================================}

procedure LAN78XXTransmitStart(Network:PLAN78XXNetwork);
{Transmit start function for the LAN78XX Network device}
{Note: Not intended to be called directly by applications}

{Note: Caller must hold the network lock}
var
 Status:LongWord;
 Request:PUSBRequest;
 Entry:PNetworkEntry;
 Packet:PNetworkPacket;
begin
 {}
 {Check Network}
 if Network = nil then Exit;

 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(@Network.Network,'LAN78XX: Transmit Start');
 {$ENDIF}
  
 {Check Count}
 if Network.Network.TransmitQueue.Count = 0 then Exit;

 {Get Entry}
 Entry:=Network.Network.TransmitQueue.Entries[Network.Network.TransmitQueue.Start];
 if Entry = nil then Exit;
   
 {Get Packet}
 Packet:=@Entry.Packets[0];
 
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if NETWORK_LOG_ENABLED then NetworkLogDebug(@Network.Network,'LAN78XX: Packet Length = ' + IntToStr(Packet.Length));
 {$ENDIF}
 
 {Get Request}
 Request:=Network.TransmitRequest;
 
 {Update Entry}
 Entry.DriverData:=Network;
 
 {Initialize Request}
 USBRequestInitialize(Request,LAN78XXTransmitComplete,Entry.Buffer,Entry.Size,Entry);
 
 {Add TX Command A and B to the start of the packet data}
 PLongWord(PtrUInt(Request.Data) + 0)^:=LongWordNToLE(Packet.Length and LAN78XX_TX_CMD_A_LEN_MASK) or LAN78XX_TX_CMD_A_FCS;
 PLongWord(PtrUInt(Request.Data) + 4)^:=0;
 
 {Update Request}
 Request.Size:=Packet.Length + LAN78XX_TX_OVERHEAD;

 {Update Pending}
 Inc(Network.PendingCount);

 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Submitting transmit request');
 {$ENDIF}
 
 {Submit the Request} 
 Status:=USBRequestSubmit(Request);
 if Status <> USB_STATUS_SUCCESS then
  begin
   if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed to submit transmit request: ' + USBStatusToString(Status));

   {Update Pending}
   Dec(Network.PendingCount);
   Exit;
  end;
end;
 
{==============================================================================}
{==============================================================================}
{LAN78XX USB Functions}
function LAN78XXDriverBind(Device:PUSBDevice;Interrface:PUSBInterface):LongWord;
{Bind the LAN78XX driver to a USB device if it is suitable}
{Device: The USB device to attempt to bind to}
{Interrface: The USB interface to attempt to bind to (or nil for whole device)}
{Return: USB_STATUS_SUCCESS if completed, USB_STATUS_DEVICE_UNSUPPORTED if unsupported or another error code on failure}
var
 Status:LongWord;
 Address:PHardwareAddress;
 Network:PLAN78XXNetwork;
 NetworkInterface:PUSBInterface;
 ReceiveEndpoint:PUSBEndpointDescriptor;
 TransmitEndpoint:PUSBEndpointDescriptor;
 InterruptEndpoint:PUSBEndpointDescriptor;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;

 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Attempting to bind USB device (Manufacturer=' + Device.Manufacturer + ' Product=' + Device.Product + ' Address=' + IntToStr(Device.Address) + ')');
 {$ENDIF}
 
 {Check Interface (Bind to device only)}
 if Interrface <> nil then
  begin
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
   if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Interface bind not supported by driver');
   {$ENDIF}
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
 
 {Check LAN78XX Device}
 if LAN78XXCheckDevice(Device) <> USB_STATUS_SUCCESS then
  begin
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
   if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Device not found in supported device list');
   {$ENDIF}
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
 
 {Check Interface}
 NetworkInterface:=USBDeviceFindInterfaceByIndex(Device,0);
 if NetworkInterface = nil then
  begin
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
   if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Device has no available interface');
   {$ENDIF}
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
 
 {Check Bulk IN Endpoint}
 ReceiveEndpoint:=USBDeviceFindEndpointByType(Device,NetworkInterface,USB_DIRECTION_IN,USB_TRANSFER_TYPE_BULK);
 if ReceiveEndpoint = nil then
  begin
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
    if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Device has no BULK IN endpoint');
   {$ENDIF}
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
 
 {Check Bulk OUT Endpoint}
 TransmitEndpoint:=USBDeviceFindEndpointByType(Device,NetworkInterface,USB_DIRECTION_OUT,USB_TRANSFER_TYPE_BULK);
 if TransmitEndpoint = nil then
  begin
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
    if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Device has no BULK OUT endpoint');
   {$ENDIF}
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
  
 {Check Interrupt IN Endpoint}
 InterruptEndpoint:=USBDeviceFindEndpointByType(Device,NetworkInterface,USB_DIRECTION_IN,USB_TRANSFER_TYPE_INTERRUPT);
 if InterruptEndpoint = nil then
  begin
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
    if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Device has no INTERRUPT IN endpoint');
   {$ENDIF}
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
  
 {Check Configuration}
 if Device.ConfigurationValue = 0 then
  begin
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
   if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Assigning configuration ' + IntToStr(Device.Configuration.Descriptor.bConfigurationValue) + ' (' + IntToStr(Device.Configuration.Descriptor.bNumInterfaces) + ' interfaces available)');
   {$ENDIF}
   
   {Set Configuration}
   Status:=USBDeviceSetConfiguration(Device,Device.Configuration.Descriptor.bConfigurationValue);
   if Status <> USB_STATUS_SUCCESS then
    begin
     if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set device configuration: ' + USBStatusToString(Status));
     
     {Return Result}
     Result:=Status;
     Exit;
    end;
  end;
 
 {USB device reset not required because the USB core already did a reset on the port during attach}
  
 {Get MAC address}
 Address:=AllocMem(SizeOf(THardwareAddress));
 LAN78XXGetMacAddress(Device,Address);
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Default Address = ' + HardwareAddressToString(Address^));
 {$ENDIF}
 
 {Check MAC Address}
 if not ValidHardwareAddress(Address^) then
  begin
   {Convert MAC address}
   Address^:=StringToHardwareAddress(LAN78XX_MAC_ADDRESS); 
   {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
   if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Hardware Address = ' + HardwareAddressToString(Address^));
   {$ENDIF}
   
   {Check MAC Address}
   if not ValidHardwareAddress(Address^) then
    begin
     {Random MAC Address}
     Address^:=RandomHardwareAddress;
     {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
     if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Random Address = ' + HardwareAddressToString(Address^));
     {$ENDIF}
    end;
    
   {Set MAC Address} 
   Status:=LAN78XXSetMacAddress(Device,Address);
   if Status <> USB_STATUS_SUCCESS then
    begin
     if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to set mac address: ' + USBStatusToString(Status));
     FreeMem(Address);
     
     {Return Result}
     Result:=USB_STATUS_DEVICE_UNSUPPORTED;
     Exit;
    end;
  end; 
 FreeMem(Address);
 
 {Create Network}
 Network:=PLAN78XXNetwork(NetworkDeviceCreateEx(SizeOf(TLAN78XXNetwork)));
 if Network = nil then
  begin
   if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to create new network device');
   
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
 
 {Update Network} 
 {Device}
 Network.Network.Device.DeviceBus:=DEVICE_BUS_USB;
 Network.Network.Device.DeviceType:=NETWORK_TYPE_ETHERNET;
 Network.Network.Device.DeviceFlags:=NETWORK_FLAG_RX_BUFFER or NETWORK_FLAG_TX_BUFFER or NETWORK_FLAG_RX_MULTIPACKET;
 Network.Network.Device.DeviceData:=Device;
 Network.Network.Device.DeviceDescription:=LAN78XX_NETWORK_DESCRIPTION;
 {Network}
 Network.Network.NetworkState:=NETWORK_STATE_CLOSED;
 Network.Network.NetworkStatus:=NETWORK_STATUS_DOWN;
 Network.Network.DeviceOpen:=LAN78XXNetworkOpen;
 Network.Network.DeviceClose:=LAN78XXNetworkClose;
 Network.Network.DeviceControl:=LAN78XXNetworkControl;
 Network.Network.BufferAllocate:=LAN78XXBufferAllocate;
 Network.Network.BufferRelease:=LAN78XXBufferRelease;
 Network.Network.BufferReceive:=LAN78XXBufferReceive;
 Network.Network.BufferTransmit:=LAN78XXBufferTransmit;
 {Driver}
 Network.PHYLock:=INVALID_HANDLE_VALUE;
 {USB}
 Network.ReceiveEndpoint:=ReceiveEndpoint;
 Network.TransmitEndpoint:=TransmitEndpoint; 
 Network.InterruptEndpoint:=InterruptEndpoint;                
 Network.WaiterThread:=INVALID_HANDLE_VALUE;
 
 {Create PHY Lock}
 Network.PHYLock:=MutexCreate;
 if Network.PHYLock = INVALID_HANDLE_VALUE then
  begin
   if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to create PHY lock for network');
   
   {Destroy Network}
   NetworkDeviceDestroy(@Network.Network);
   
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
 
 {Register Network} 
 if NetworkDeviceRegister(@Network.Network) <> ERROR_SUCCESS then
  begin
   if USB_LOG_ENABLED then USBLogError(Device,'LAN78XX: Failed to register new network device');
   
   {Destroy Network}
   NetworkDeviceDestroy(@Network.Network);
   
   {Return Result}
   Result:=USB_STATUS_DEVICE_UNSUPPORTED;
   Exit;
  end;
 
 {Update Device}
 Device.DriverData:=Network;
 
 {Return Result}
 Result:=USB_STATUS_SUCCESS;
end;

{==============================================================================}

function LAN78XXDriverUnbind(Device:PUSBDevice;Interrface:PUSBInterface):LongWord;
{Unbind the LAN78XX driver from a USB device}
{Device: The USB device to unbind from}
{Interrface: The USB interface to unbind from (or nil for whole device)}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}
var
 Network:PLAN78XXNetwork;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;
 
 {Check Device}
 if Device = nil then Exit;

 {Check Interface}
 if Interrface <> nil then Exit;
 
 {Check Driver}
 if Device.Driver <> LAN78XXDriver then Exit;
 
 {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
 if USB_LOG_ENABLED then USBLogDebug(Device,'LAN78XX: Unbinding USB device (Manufacturer=' + Device.Manufacturer + ' Product=' + Device.Product + ' Address=' + IntToStr(Device.Address) + ')');
 {$ENDIF}
 
 {Get Network}
 Network:=PLAN78XXNetwork(Device.DriverData);
 if Network = nil then Exit;
 
 {Close Network}
 LAN78XXNetworkClose(@Network.Network);
 
 {Destroy PHY Lock}
 if Network.PHYLock <> INVALID_HANDLE_VALUE then
  begin
   MutexDestroy(Network.PHYLock);
  end;
  
 {Update Device}
 Device.DriverData:=nil;

 {Deregister Network}
 if NetworkDeviceDeregister(@Network.Network) <> ERROR_SUCCESS then Exit;
 
 {Destroy Network}
 NetworkDeviceDestroy(@Network.Network);
 
 Result:=USB_STATUS_SUCCESS;
end;

{==============================================================================}

procedure LAN78XXReceiveWorker(Request:PUSBRequest); 
{Called (by a Worker thread) to process a completed USB request from the LAN78XX bulk IN endpoint}
{Request: The USB request which has completed}
var
 Data:Pointer;
 Size:LongWord;
 Status:LongWord;
 Message:TMessage;
 AlignCount:LongWord;
 FrameLength:LongWord;
 ReceiveStatus:LongWord;
 Next:PNetworkEntry;
 Entry:PNetworkEntry;
 Network:PLAN78XXNetwork;
begin
 {}
 {Check Request}
 if Request = nil then Exit;

 {Get Entry}
 Entry:=PNetworkEntry(Request.DriverData);
 if Entry = nil then Exit;
 
 {Get Network}
 Network:=PLAN78XXNetwork(Entry.DriverData);
 if Network <> nil then 
  begin
   {Acquire the Lock}
   if MutexLock(Network.Network.Lock) = ERROR_SUCCESS then
    begin
     try
      {Check State}
      if Network.Network.NetworkState = NETWORK_STATE_CLOSING then
       begin
        {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
        if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Close pending, setting receive request status to USB_STATUS_DEVICE_DETACHED');
        {$ENDIF}
      
        {Update Request}
        Request.Status:=USB_STATUS_DEVICE_DETACHED;
       end;
       
      {Check Result} 
      if Request.Status = USB_STATUS_SUCCESS then
       begin
        {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
        if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Receive complete (Size=' + IntToStr(Request.Size) + ' Actual Size=' + IntToStr(Request.ActualSize) + ')');
        {$ENDIF}
 
        {Get Data and Size}
        Data:=Request.Data;
        Size:=Request.ActualSize;
        if Size > 0 then
         begin
          {Get Next}
          Next:=nil;
          if BufferAvailable(Network.Network.ReceiveQueue.Buffer) > 0 then
           begin
            Next:=BufferGet(Network.Network.ReceiveQueue.Buffer);
           end;
          
          {Check Next} 
          if Next <> nil then
           begin
            {Check Receive Queue Count}
            if Network.Network.ReceiveQueue.Count < Network.ReceiveEntryCount then
             begin
              {Update Entry}
              Entry.Count:=0;
              
              while Size > 0 do
               begin
                {Get Frame Length and Status from RX Command A}
                FrameLength:=LongWordLEToN(PLongWord(PtrUInt(Data) + 0)^) and LAN78XX_RX_CMD_A_LEN_MASK;
                ReceiveStatus:=LongWordLEToN(PLongWord(PtrUInt(Data) + 0)^) and (LAN78XX_RX_CMD_A_RED or LAN78XX_RX_CMD_A_RX_ERRS_MASK);
                
                {Check Receive Status and Frame Length}
                if ((ReceiveStatus and LAN78XX_RX_CMD_A_RED) <> 0) or (FrameLength > (ETHERNET_MAX_PACKET_SIZE + ETHERNET_CRC_SIZE)) or (FrameLength < (ETHERNET_HEADER_SIZE + ETHERNET_CRC_SIZE)) then
                 begin
                  if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Receive error (ReceiveStatus=' + IntToHex(ReceiveStatus,8) + ', FrameLength=' + IntToStr(FrameLength) + ')');
                  
                  {Update Statistics}
                  Inc(Network.Network.ReceiveErrors); 
                 end
                else if Entry.Count >= Network.ReceivePacketCount then
                 begin
                  if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Buffer overrun, packet discarded');
             
                  {Update Statistics}
                  Inc(Network.Network.BufferOverruns); 
                 end
                else
                 begin
                  {Update Entry}
                  Inc(Entry.Count);
                  
                  {Update Packet}
                  Entry.Packets[Entry.Count - 1].Buffer:=Data;
                  Entry.Packets[Entry.Count - 1].Data:=Data + Entry.Offset;
                  Entry.Packets[Entry.Count - 1].Length:=FrameLength - ETHERNET_CRC_SIZE;
                  
                  {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
                  if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Receiving packet (Length=' + IntToStr(Entry.Packets[Entry.Count - 1].Length) + ', Count=' + IntToStr(Entry.Count) + ')');
                  {$ENDIF}
                  
                  {Update Statistics}
                  Inc(Network.Network.ReceiveCount); 
                  Inc(Network.Network.ReceiveBytes,Entry.Packets[Entry.Count - 1].Length); 
                 end;
                
                {Update Data and Size}
                Inc(Data,LAN78XX_RX_OVERHEAD);
                Dec(Size,LAN78XX_RX_OVERHEAD);
                
                {Update Size}
                Dec(Size,FrameLength);
                
                {Check Size}
                if Size > 0 then
                 begin
                  {Get Next Packet}
                  Inc(Data,FrameLength);
                  
                  {Check padding before next packet}
                  AlignCount:=(4 - ((FrameLength + LAN78XX_RXW_PADDING) mod 4)) mod 4;
                  if AlignCount > 0 then
                   begin
                    {Update Data and Size}
                    Inc(Data,AlignCount);
                    Dec(Size,AlignCount);
                   end; 
                 end;
               end;
               
              {Check Count}
              if Entry.Count > 0 then
               begin
                {Add Entry}
                Network.Network.ReceiveQueue.Entries[(Network.Network.ReceiveQueue.Start + Network.Network.ReceiveQueue.Count) mod Network.ReceiveEntryCount]:=Entry;
                    
                {Update Count}
                Inc(Network.Network.ReceiveQueue.Count);
                    
                {Signal Packet Received}
                SemaphoreSignal(Network.Network.ReceiveQueue.Wait);
               end
              else
               begin
                {Free Entry}
                BufferFree(Entry);
               end;
             end
            else
             begin
              if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Receive queue overrun, packet discarded');
              
              {Free Entry}
              BufferFree(Entry);
              
              {Update Statistics}
              Inc(Network.Network.ReceiveErrors); 
              Inc(Network.Network.BufferOverruns); 
             end;
           end
          else
           begin
            if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: No receive buffer available, packet discarded');
            
            {Get Next}
            Next:=Entry;
            
            {Update Statistics}
            Inc(Network.Network.ReceiveErrors); 
            Inc(Network.Network.BufferUnavailable); 
           end;
         end
        else
         begin
          if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed receive request (ActualSize=' + USBStatusToString(Request.ActualSize) + ')');
          
          {Get Next}
          Next:=Entry;
   
          {Update Statistics}
          Inc(Network.Network.ReceiveErrors); 
         end;
       end
      else 
       begin
        if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed receive request (Status=' + USBStatusToString(Request.Status) + ')');
   
        {Get Next}
        Next:=Entry;
   
        {Update Statistics}
        Inc(Network.Network.ReceiveErrors); 
       end;
 
      {Update Pending}
      Dec(Network.PendingCount); 
 
      {Update Next}
      Next.DriverData:=nil;
 
      {Check State}
      if Network.Network.NetworkState = NETWORK_STATE_CLOSING then
       begin
        {Free Next}
        BufferFree(Next);
        
        {Check Pending}
        if Network.PendingCount = 0 then
         begin
          {Check Waiter}
          if Network.WaiterThread <> INVALID_HANDLE_VALUE then
           begin
            {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
            if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Close pending, sending message to waiter thread (Thread=' + IntToHex(Network.WaiterThread,8) + ')');
            {$ENDIF}
            
            {Send Message}
            FillChar(Message,SizeOf(TMessage),0);
            ThreadSendMessage(Network.WaiterThread,Message);
            Network.WaiterThread:=INVALID_HANDLE_VALUE;
           end; 
         end;
       end
      else
       begin      
        {Check Next} 
        if Next <> nil then
         begin
          {Update Pending}
          Inc(Network.PendingCount);
   
          {Update Next}
          Next.DriverData:=Network;
   
          {Initialize Request}
          USBRequestInitialize(Request,LAN78XXReceiveComplete,Next.Buffer,Next.Size,Next);
          
          {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
          if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Resubmitting receive request');
          {$ENDIF}
  
          {Resubmit Request}
          Status:=USBRequestSubmit(Request);
          if Status <> USB_STATUS_SUCCESS then
           begin
            if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed to resubmit receive request: ' + USBStatusToString(Status));
     
            {Update Pending}
            Dec(Network.PendingCount);
            
            {Update Next}
            Next.DriverData:=nil;
            
            {Free Next}
            BufferFree(Next);
           end;
         end
        else
         begin
          if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: No receive buffer available, cannot resubmit receive request');
          
          {Update Statistics}
          Inc(Network.Network.BufferUnavailable); 
         end;
       end;  
     finally
      {Release the Lock}
      MutexUnlock(Network.Network.Lock);
     end;
    end
   else
    begin
     if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed to acquire lock');
    end;
  end
 else
  begin
   if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Receive request invalid');
  end;    
end;
 
{==============================================================================}

procedure LAN78XXReceiveComplete(Request:PUSBRequest);
{Called when a USB request from the LAN78XX bulk IN endpoint completes}
{Request: The USB request which has completed}
{Note: Request is passed to worker thread for processing to prevent blocking the USB completion}
begin
 {}
 {Check Request}
 if Request = nil then Exit;
 
 WorkerScheduleEx(0,WORKER_FLAG_PRIORITY,TWorkerTask(LAN78XXReceiveWorker),Request,nil);
end;

{==============================================================================}

procedure LAN78XXTransmitWorker(Request:PUSBRequest); 
{Called (by a Worker thread) to process a completed USB request to the LAN78XX bulk OUT endpoint}
{Request: The USB request which has completed}
var
 Message:TMessage;
 Entry:PNetworkEntry;
 Network:PLAN78XXNetwork;
begin
 {}
 {Check Request}
 if Request = nil then Exit;

 {Get Entry}
 Entry:=PNetworkEntry(Request.DriverData);
 if Entry = nil then Exit;
 
 {Get Network}
 Network:=PLAN78XXNetwork(Entry.DriverData);
 if Network <> nil then
  begin
   {Acquire the Lock}
   if MutexLock(Network.Network.Lock) = ERROR_SUCCESS then
    begin
     try
      {Check State}
      if Network.Network.NetworkState = NETWORK_STATE_CLOSING then
       begin
        {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
        if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Close pending, setting transmit request status to USB_STATUS_DEVICE_DETACHED');
        {$ENDIF}
      
        {Update Request}
        Request.Status:=USB_STATUS_DEVICE_DETACHED;
       end;
      
      {Check Result} 
      if Request.Status = USB_STATUS_SUCCESS then
       begin
        {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
        if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Transmit complete');
        {$ENDIF}
        
        {Update Statistics}
        Inc(Network.Network.TransmitCount); 
        Inc(Network.Network.TransmitBytes,Entry.Packets[0].Length);
       end
      else 
       begin
        if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed transmit request (Status=' + USBStatusToString(Request.Status) + ')');
        
        {Update Statistics}
        Inc(Network.Network.TransmitErrors); 
       end;

      {Update Start}
      Network.Network.TransmitQueue.Start:=(Network.Network.TransmitQueue.Start + 1) mod PLAN78XXNetwork(Network).TransmitEntryCount;
      
      {Update Count}
      Dec(Network.Network.TransmitQueue.Count);
      
      {Signal Queue Free}
      SemaphoreSignal(Network.Network.TransmitQueue.Wait); 

      {Update Entry}
      Entry.DriverData:=nil;
      
      {Free Entry (Transmit Buffer)}
      BufferFree(Entry);
       
      {Update Pending}
      Dec(Network.PendingCount); 
     
      {Check State}
      if Network.Network.NetworkState = NETWORK_STATE_CLOSING then
       begin
        {Check Pending}
        if Network.PendingCount = 0 then
         begin
          {Check Waiter}
          if Network.WaiterThread <> INVALID_HANDLE_VALUE then
           begin
            {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
            if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Close pending, sending message to waiter thread (Thread=' + IntToHex(Network.WaiterThread,8) + ')');
            {$ENDIF}
            
            {Send Message}
            FillChar(Message,SizeOf(TMessage),0);
            ThreadSendMessage(Network.WaiterThread,Message);
            Network.WaiterThread:=INVALID_HANDLE_VALUE;
           end; 
         end;
       end
      else
       begin
        {Check Count}
        if Network.Network.TransmitQueue.Count > 0 then
         begin
          {Start Transmit}
          LAN78XXTransmitStart(Network);
         end;
       end;
     finally
      {Release the Lock}
      MutexUnlock(Network.Network.Lock);
     end;
    end
   else
    begin
     if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed to acquire lock');
    end;
  end
 else
  begin
   if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Transmit request invalid');
  end;    
end;
 
{==============================================================================}

procedure LAN78XXTransmitComplete(Request:PUSBRequest);
{Called when a USB request to the LAN78XX bulk OUT endpoint completes}
{Request: The USB request which has completed}
{Note: Request is passed to worker thread for processing to prevent blocking the USB completion}
begin
 {}
 {Check Request}
 if Request = nil then Exit;
 
 WorkerScheduleEx(0,WORKER_FLAG_PRIORITY,TWorkerTask(LAN78XXTransmitWorker),Request,nil);
end;

{==============================================================================}

procedure LAN78XXInterruptWorker(Request:PUSBRequest);
{Called (by a Worker thread) to process a completed USB request from the LAN78XX interrupt IN endpoint}
{Request: The USB request which has completed}
var
 Value:Word;
 Mask:LongWord;
 Status:LongWord;
 Message:TMessage;
 Network:PLAN78XXNetwork;
begin
 {}
 {Check Request}
 if Request = nil then Exit;

 {Get Network}
 Network:=PLAN78XXNetwork(Request.DriverData);
 if Network <> nil then
  begin
   {Acquire the Lock}
   if MutexLock(Network.Network.Lock) = ERROR_SUCCESS then
    begin
     try
      {Update Statistics}
      Inc(Network.Network.StatusCount); 

      {Check State}
      if Network.Network.NetworkState = NETWORK_STATE_CLOSING then
       begin
        {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
        if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Close pending, setting interrupt request status to USB_STATUS_DEVICE_DETACHED');
        {$ENDIF}
      
        {Update Request}
        Request.Status:=USB_STATUS_DEVICE_DETACHED;
       end;
      
      {Check Result} 
      if Request.Status = USB_STATUS_SUCCESS then
       begin
        {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
        if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Interrupt complete (Size=' + IntToStr(Request.Size) + ' Actual Size=' + IntToStr(Request.ActualSize) + ')');
        {$ENDIF}

        {Clear PHY Interrupt}
        LAN78XXPHYRead(Request.Device,LAN88XX_INT_STS,Value);
        
        {Clear Interrupt Status}
        LAN78XXWriteRegister(Request.Device,LAN78XX_INT_STS,LAN78XX_INT_STS_CLEAR_ALL);
        
        {Check Size}
        if Request.ActualSize <> 4 then
         begin
          if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Interrupt error (Size=' + IntToStr(Request.ActualSize) + ')');
         
          {Update Statistics}
          Inc(Network.Network.StatusErrors); 
         end
        else
         begin
          {Get Interrupt Mask}
          Mask:=LongWordLEToN(PLongWord(Request.Data)^);
          
          {Check for PHY Interrupt}
          if (Mask and LAN78XX_INT_ENP_PHY_INT) <> 0 then
           begin
            {Get Network Status}
            LAN78XXPHYRead(Request.Device,MII_BMSR,Value);
            if (Value and BMSR_LSTATUS) <> 0 then
             begin
              {Check Status}
              if Network.Network.NetworkStatus <> NETWORK_STATUS_UP then
               begin
                {Set Status to Up}
                Network.Network.NetworkStatus:=NETWORK_STATUS_UP;
              
                {Notify the Status}
                NotifierNotify(@Network.Network.Device,DEVICE_NOTIFICATION_UP); 
               end; 
             end
            else
             begin
              {Check Status}
              if Network.Network.NetworkStatus <> NETWORK_STATUS_DOWN then
               begin
                {Set Status to Down}
                Network.Network.NetworkStatus:=NETWORK_STATUS_DOWN;
              
                {Notify the Status}
                NotifierNotify(@Network.Network.Device,DEVICE_NOTIFICATION_DOWN); 
               end; 
             end;
           end
          else
           begin
            if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Interrupt error - unknown interrupt (Mask=' + IntToHex(Mask,8) + ')');
            
            {Update Statistics}
            Inc(Network.Network.StatusErrors); 
           end;
         end;
       end
      else 
       begin
        if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed interrupt request (Status=' + USBStatusToString(Request.Status) + ')');
        
        {Update Statistics}
        Inc(Network.Network.StatusErrors); 
       end;
     
      {Update Pending}
      Dec(Network.PendingCount); 
     
      {Check State}
      if Network.Network.NetworkState = NETWORK_STATE_CLOSING then
       begin
        {Check Pending}
        if Network.PendingCount = 0 then
         begin
          {Check Waiter}
          if Network.WaiterThread <> INVALID_HANDLE_VALUE then
           begin
            {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
            if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Close pending, sending message to waiter thread (Thread=' + IntToHex(Network.WaiterThread,8) + ')');
            {$ENDIF}
            
            {Send Message}
            FillChar(Message,SizeOf(TMessage),0);
            ThreadSendMessage(Network.WaiterThread,Message);
            Network.WaiterThread:=INVALID_HANDLE_VALUE;
           end; 
         end;
       end
      else
       begin
        {Update Pending}
        Inc(Network.PendingCount);
 
        {$IF DEFINED(LAN78XX_DEBUG) or DEFINED(NETWORK_DEBUG)}
        if USB_LOG_ENABLED then USBLogDebug(Request.Device,'LAN78XX: Resubmitting interrupt request');
        {$ENDIF}

        {Resubmit Request}
        Status:=USBRequestSubmit(Request);
        if Status <> USB_STATUS_SUCCESS then
         begin
          if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed to resubmit interrupt request: ' + USBStatusToString(Status));
   
          {Update Pending}
          Dec(Network.PendingCount);
         end;
       end;
     finally
      {Release the Lock}
      MutexUnlock(Network.Network.Lock);
     end;
    end
   else
    begin
     if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Failed to acquire lock');
    end;
  end
 else
  begin
   if USB_LOG_ENABLED then USBLogError(Request.Device,'LAN78XX: Interrupt request invalid');
  end;    
end;

{==============================================================================}

procedure LAN78XXInterruptComplete(Request:PUSBRequest);
{Called when a USB request from the LAN78XX interrupt IN endpoint completes}
{Request: The USB request which has completed}
{Note: Request is passed to worker thread for processing to prevent blocking the USB completion}
begin
 {}
 {Check Request}
 if Request = nil then Exit;
 
 WorkerScheduleEx(0,WORKER_FLAG_PRIORITY,TWorkerTask(LAN78XXInterruptWorker),Request,nil);
end;

{==============================================================================}
{==============================================================================}
{LAN78XX Helper Functions}
function LAN78XXCheckDevice(Device:PUSBDevice):LongWord;
{Check the Vendor and Device ID against the supported devices}
{Device: USB device to check}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Count:Integer;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;
 
 {Check Device}
 if Device = nil then Exit;

 {Check Device IDs}
 for Count:=0 to LAN78XX_DEVICE_ID_COUNT - 1 do
  begin
   if (LAN78XX_DEVICE_ID[Count].idVendor = Device.Descriptor.idVendor) and (LAN78XX_DEVICE_ID[Count].idProduct = Device.Descriptor.idProduct) then
    begin
     Result:=USB_STATUS_SUCCESS;
     Exit;
    end;
  end;

 Result:=USB_STATUS_DEVICE_UNSUPPORTED;
end;
    
{==============================================================================}

function LAN78XXReadRegister(Device:PUSBDevice;Index:LongWord;var Data:LongWord):LongWord;
{Read from a register on a LAN78XX USB Ethernet Adapter}
{Device: USB device to read from}
{Index: Index of the register to read}
{Data: Value to return the register contents}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;

 {Send Read Register Request}
 Result:=USBControlRequest(Device,nil,LAN78XX_USB_VENDOR_REQUEST_READ_REGISTER,USB_BMREQUESTTYPE_DIR_IN or USB_BMREQUESTTYPE_TYPE_VENDOR or USB_BMREQUESTTYPE_RECIPIENT_DEVICE,0,Index,@Data,SizeOf(LongWord));
end;

{==============================================================================}

function LAN78XXWriteRegister(Device:PUSBDevice;Index,Data:LongWord):LongWord;
{Write to a register on a LAN78XX USB Ethernet Adapter}
{Device: USB device to write to}
{Index: Index of the register to write}
{Data: Value to write to the register}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Send Write Register Request}
 Result:=USBControlRequest(Device,nil,LAN78XX_USB_VENDOR_REQUEST_WRITE_REGISTER,USB_BMREQUESTTYPE_DIR_OUT or USB_BMREQUESTTYPE_TYPE_VENDOR or USB_BMREQUESTTYPE_RECIPIENT_DEVICE,0,Index,@Data,SizeOf(LongWord));
end;
    
{==============================================================================}
    
function LAN78XXModifyRegister(Device:PUSBDevice;Index,Mask,Value:LongWord):LongWord;
{Modify the value contained in a register on a LAN78XX USB Ethernet Adapter}
{Device: USB device to modify}
{Index: Index of the register to modify}
{Mask: Mask that contains 1 for the bits where the old value in the register
       will be kept rather than cleared (unless those bits also appear in 
       Value, in which case they will still be set)}
{Value: Mask of bits to set in the register}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Data:LongWord;
 Status:LongWord;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Read Register}
 Status:=LAN78XXReadRegister(Device,Index,Data);
 if Status <> USB_STATUS_SUCCESS then
  begin
   Result:=Status;
   Exit;
  end;
  
 {Modify Value}
 Data:=Data and Mask;
 Data:=Data or Value;
 
 {Write Register}
 Result:=LAN78XXWriteRegister(Device,Index,Data);
end;
    
{==============================================================================}

function LAN78XXSetRegisterBits(Device:PUSBDevice;Index,Value:LongWord):LongWord;
{Set bits in a register on a LAN78XX USB Ethernet Adapter}
{Device: USB device to write to}
{Index: Index of the register to modify}
{Value: Bits to set in the register.  At positions where there is a 0, the old
        value in the register will be written}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Modify Register}
 Result:=LAN78XXModifyRegister(Device,Index,$FFFFFFFF,Value);
end;

{==============================================================================}

function LAN78XXClearRegisterBits(Device:PUSBDevice;Index,Value:LongWord):LongWord;
{Clear bits in a register on a LAN78XX USB Ethernet Adapter}
{Device: USB device to write to}
{Index: Index of the register to modify}
{Value: Bits to clear in the register.  At positions where there is a 0, the old
        value in the register will be written}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Modify Register}
 Result:=LAN78XXModifyRegister(Device,Index,not(Value),0);
end;
    
{==============================================================================}
    
function LAN78XXPHYRead(Device:PUSBDevice;Index:LongWord;var Value:Word):LongWord;
{Read a register from the MII Management serial interface on a LAN78XX USB Ethernet Adapter}
{Device: USB device to read from}
{Index: Index of the register to read}
{Value: Value to return the register contents}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Data:LongWord;
 Status:LongWord;
 Address:LongWord;
 Network:PLAN78XXNetwork;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Setup Value}
 Value:=0;
 
 {Check Device}
 if Device = nil then Exit;
 
 {Get Network}
 Network:=PLAN78XXNetwork(Device.DriverData);
 if Network = nil then Exit;
 
 {Acquire PHY Lock}
 if MutexLock(Network.PHYLock) = ERROR_SUCCESS then
  begin
   try
    {Wait for MII not busy}
    Status:=LAN78XXPHYWaitNotBusy(Device);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
     
    {Set the Address, Index and Direction (Read from PHY)}
    Address:=(LAN78XX_PHY_ADDRESS shl LAN78XX_MII_ACC_PHY_ADDR_SHIFT) and LAN78XX_MII_ACC_PHY_ADDR_MASK;
    Address:=Address or (Index shl LAN78XX_MII_ACC_MIIRINDA_SHIFT) and LAN78XX_MII_ACC_MIIRINDA_MASK;
    Address:=Address or LAN78XX_MII_ACC_MII_READ or LAN78XX_MII_ACC_MII_BUSY;
    Status:=LAN78XXWriteRegister(Device,LAN78XX_MII_ACC,Address);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
    
    {Wait for MII not busy}
    Status:=LAN78XXPHYWaitNotBusy(Device);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
     
    {Read the Data}
    Status:=LAN78XXReadRegister(Device,LAN78XX_MII_DATA,Data);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
    
    {Return Value}
    Value:=Data and $FFFF;
    
    {Return Result}
    Result:=USB_STATUS_SUCCESS;
   finally
    {Release PHY Lock}
    MutexUnlock(Network.PHYLock);
   end;
  end;
end;

{==============================================================================}

function LAN78XXPHYWrite(Device:PUSBDevice;Index:LongWord;Value:Word):LongWord;
{Write a register to the MII Management serial interface on a LAN78XX USB Ethernet Adapter}
{Device: USB device to write to}
{Index: Index of the register to write}
{Value: Value to write to the register}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Status:LongWord;
 Address:LongWord;
 Network:PLAN78XXNetwork;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Get Network}
 Network:=PLAN78XXNetwork(Device.DriverData);
 if Network = nil then Exit;
 
 {Acquire PHY Lock}
 if MutexLock(Network.PHYLock) = ERROR_SUCCESS then
  begin
   try
    {Wait for MII not busy}
    Status:=LAN78XXPHYWaitNotBusy(Device);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
    
    {Write the Data}
    Status:=LAN78XXWriteRegister(Device,LAN78XX_MII_DATA,Value);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
    
    {Set the Address, Index and Direction (Write to PHY)}
    Address:=(LAN78XX_PHY_ADDRESS shl LAN78XX_MII_ACC_PHY_ADDR_SHIFT) and LAN78XX_MII_ACC_PHY_ADDR_MASK;
    Address:=Address or (Index shl LAN78XX_MII_ACC_MIIRINDA_SHIFT) and LAN78XX_MII_ACC_MIIRINDA_MASK;
    Address:=Address or LAN78XX_MII_ACC_MII_WRITE or LAN78XX_MII_ACC_MII_BUSY;
    Status:=LAN78XXWriteRegister(Device,LAN78XX_MII_ACC,Address);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
    
    {Wait for MII not busy}
    Status:=LAN78XXPHYWaitNotBusy(Device);
    if Status <> USB_STATUS_SUCCESS then
     begin
      Result:=Status;
      Exit;
     end;
    
    {Return Result}
    Result:=USB_STATUS_SUCCESS;
   finally
    {Release PHY Lock}
    MutexUnlock(Network.PHYLock);
   end;
  end;
end;

{==============================================================================}

function LAN78XXPHYInitialize(Device:PUSBDevice):LongWord;
{Initialize default MII Management serial interface options on a LAN78XX USB Ethernet Adapter}
{Device: USB device to initialize}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Value:Word;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Clear Interrupt Status}
 LAN78XXPHYRead(Device,LAN88XX_INT_STS,Value);
 
 {Enable PHY Interrupts}
 LAN78XXPHYWrite(Device,LAN88XX_INT_MASK,LAN88XX_INT_MASK_MDINTPIN_EN or LAN88XX_INT_MASK_LINK_CHANGE);

 {Enable Auto MDIX} 
 LAN78XXPHYWrite(Device,LAN88XX_EXT_PAGE_ACCESS,LAN88XX_EXT_PAGE_SPACE_1);
 
 LAN78XXPHYRead(Device,LAN88XX_EXT_MODE_CTRL,Value);
 Value:=Value and not(LAN88XX_EXT_MODE_CTRL_MDIX_MASK);
 LAN78XXPHYWrite(Device,LAN88XX_EXT_MODE_CTRL,Value or LAN88XX_EXT_MODE_CTRL_AUTO_MDIX);
 
 LAN78XXPHYWrite(Device,LAN88XX_EXT_PAGE_ACCESS,LAN88XX_EXT_PAGE_SPACE_0);

 {Change LED defaults:
    orange = link1000/activity
    green  = link10/link100/activity
   bits: 0-3 = orange
         4-7 = green
   led: 0=link/activity          1=link1000/activity
        2=link100/activity       3=link10/activity
        4=link100/1000/activity  5=link10/1000/activity
        6=link10/100/activity    14=off    15=on}
 LAN78XXPHYRead(Device,$1D,Value);
 Value:=Value and not($FF);
 Value:=Value or (1 shl 0) or (6 shl 4);
 LAN78XXPHYWrite(Device,$1D,Value);
 
 {Return Result}
 Result:=USB_STATUS_SUCCESS;
end;
    
{==============================================================================}
    
function LAN78XXPHYWaitNotBusy(Device:PUSBDevice):LongWord;
{Wait for the MII Management serial interface to be not busy on a LAN78XX USB Ethernet Adapter}
{Device: USB device to wait for}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      

{Note: Caller must hold the PHY Lock}
var
 Current:Int64;
 Value:LongWord;
 Status:LongWord;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Wait for MII Busy}
 Current:=GetTickCount64;
 repeat
  Status:=LAN78XXReadRegister(Device,LAN78XX_MII_ACC,Value);
  if Status <> USB_STATUS_SUCCESS then
   begin
    Result:=Status;
    Exit;
   end;
  
  if (Value and LAN78XX_MII_ACC_MII_BUSY) = 0 then
   begin
    Result:=USB_STATUS_SUCCESS;
    Exit;
   end;
 until GetTickCount64 > (Current + MILLISECONDS_PER_SECOND);
 
 Result:=USB_STATUS_TIMEOUT;
end;

{==============================================================================}
    
function LAN78XXReadEEPROM(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;
{Read from the EEPROM (if present) on a LAN78XX USB Ethernet Adapter}
{Device: USB device to read from}
{Offset: The byte offset to start reading}
{Length: The number of bytes to read}
{Data: Pointer to a buffer to receive the data}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Signature:Byte;
begin
 {}
 Result:=LAN78XXReadRawEEPROM(Device,0,1,@Signature);
 if (Result = USB_STATUS_SUCCESS) and (Signature = LAN78XX_EEPROM_INDICATOR) then
  begin
   Result:=LAN78XXReadRawEEPROM(Device,Offset,Length,Data);  
  end
 else
  begin
   Result:=USB_STATUS_INVALID_PARAMETER;
  end;  
end;

{==============================================================================}

function LAN78XXReadRawEEPROM(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;
{Read from the EEPROM (if present) without signature check on a LAN78XX USB Ethernet Adapter}
{Device: USB device to read from}
{Offset: The byte offset to start reading}
{Length: The number of bytes to read}
{Data: Pointer to a buffer to receive the data}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;
 
 {Check Device}
 if Device = nil then Exit;
 
 //To Do //lan78xx_read_raw_eeprom
end;

{==============================================================================}

function LAN78XXReadOTP(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;
{Read from the OTP (if present) on a LAN78XX USB Ethernet Adapter}    
{Device: USB device to read from}    
{Offset: The byte offset to start reading}
{Length: The number of bytes to read}
{Data: Pointer to a buffer to receive the data}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Signature:Byte;
begin
 {}
 Result:=LAN78XXReadRawOTP(Device,0,1,@Signature);
 if Result = USB_STATUS_SUCCESS then
  begin
   if Signature = LAN78XX_OTP_INDICATOR_1 then
    begin
     Offset:=Offset;
    end
   else if Signature = LAN78XX_OTP_INDICATOR_2 then
    begin
     Offset:=Offset + $100;
    end
   else
    begin
     Result:=USB_STATUS_INVALID_PARAMETER;
     Exit;
    end;
   
   Result:=LAN78XXReadRawOTP(Device,Offset,Length,Data);  
  end;
end;

{==============================================================================}

function LAN78XXReadRawOTP(Device:PUSBDevice;Offset,Length:LongWord;Data:PByte):LongWord;
{Read from the OTP (if present) without signature check on a LAN78XX USB Ethernet Adapter}
{Device: USB device to read from}
{Offset: The byte offset to start reading}
{Length: The number of bytes to read}
{Data: Pointer to a buffer to receive the data}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;
 
 {Check Device}
 if Device = nil then Exit;
 
 //To Do //lan78xx_read_raw_otp
end;

{==============================================================================}
    
function LAN78XXGetStatistics(Device:PUSBDevice;var Statistics:TLAN78XXStatistics):LongWord;
{Get the statistics from a LAN78XX USB Ethernet Adapter}
{Device: USB device to read from}
{Statistics: The returned statistics from the device}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;

 {Send Read Statistics Request}
 Result:=USBControlRequest(Device,nil,LAN78XX_USB_VENDOR_REQUEST_GET_STATS,USB_BMREQUESTTYPE_DIR_IN or USB_BMREQUESTTYPE_TYPE_VENDOR or USB_BMREQUESTTYPE_RECIPIENT_DEVICE,0,0,@Statistics,SizeOf(TLAN78XXStatistics));
end;
    
{==============================================================================}

function LAN78XXGetMacAddress(Device:PUSBDevice;Address:PHardwareAddress):LongWord;
{Get the MAC address of a LAN78XX USB Ethernet Adapter}
{Device: USB device read from}
{Address: Value to read the MAC address into}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}
var
 Status:LongWord;
 AddressLow:LongWord;
 AddressHigh:LongWord;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
 
 {Read Address Low}
 Status:=LAN78XXReadRegister(Device,LAN78XX_RX_ADDRL,AddressLow);
 if Status <> USB_STATUS_SUCCESS then
  begin
   Result:=Status;
   Exit;
  end;
 
 {Read Address High}
 Status:=LAN78XXReadRegister(Device,LAN78XX_RX_ADDRH,AddressHigh);
 if Status <> USB_STATUS_SUCCESS then
  begin
   Result:=Status;
   Exit;
  end;
 
 {Decode Address}
 Address[0]:=(AddressLow shr 0) and $ff;
 Address[1]:=(AddressLow shr 8) and $ff;
 Address[2]:=(AddressLow shr 16) and $ff;
 Address[3]:=(AddressLow shr 24) and $ff;
 Address[4]:=(AddressHigh shr 0) and $ff;
 Address[5]:=(AddressHigh shr 8) and $ff;
 
 {Return Result}
 Result:=USB_STATUS_SUCCESS;
end;
    
{==============================================================================}

function LAN78XXSetMacAddress(Device:PUSBDevice;Address:PHardwareAddress):LongWord;
{Set the MAC address of a LAN78XX USB Ethernet Adapter}
{Device: USB device to write to}
{Address: MAC address value to set}
{Return: USB_STATUS_SUCCESS if completed or another error code on failure}      
var
 Status:LongWord;
 AddressLow:LongWord;
 AddressHigh:LongWord;
begin
 {}
 Result:=USB_STATUS_INVALID_PARAMETER;

 {Check Device}
 if Device = nil then Exit;
    
 {Encode Address} 
 AddressLow:=Address[0] or (Address[1] shl 8) or (Address[2] shl 16) or (Address[3] shl 24);
 AddressHigh:=Address[4] or (Address[5] shl 8);
 
 {Write Address Low}
 Status:=LAN78XXWriteRegister(Device,LAN78XX_RX_ADDRL,AddressLow);
 if Status <> USB_STATUS_SUCCESS then
  begin
   Result:=Status;
   Exit;
  end;
 
 {Write Address High} 
 Status:=LAN78XXWriteRegister(Device,LAN78XX_RX_ADDRH,AddressHigh);
 if Status <> USB_STATUS_SUCCESS then
  begin
   Result:=Status;
   Exit;
  end;
 
 {Add MAC Address Perfect Filter Entry}
 Status:=LAN78XXWriteRegister(Device,LAN78XX_MAF_LO(0),AddressLow);
 if Status <> USB_STATUS_SUCCESS then
  begin
   Result:=Status;
   Exit;
  end;
 
 Result:=LAN78XXWriteRegister(Device,LAN78XX_MAF_HI(0),AddressHigh or LAN78XX_MAF_HI_VALID);
end;
       
{==============================================================================}
{==============================================================================}

initialization
 LAN78XXInit;
 
{==============================================================================}
 
finalization
 {Nothing}
 
{==============================================================================}
{==============================================================================}

end.    